Tecnicas para el Dise.o de Algoritmos - cs.buap.mxiolmos/ada/TecnicasDisenoAlgoritmos.pdf ·...

41
1 Técnicas para el Diseño de Algoritmos Algoritmos Algoritmos voraces Divide y conquista Programación dinámica Backtracking

Transcript of Tecnicas para el Dise.o de Algoritmos - cs.buap.mxiolmos/ada/TecnicasDisenoAlgoritmos.pdf ·...

1

Técnicas para el Diseño de Algoritmos

Algoritmos

Algoritmos voracesDivide y conquistaProgramación dinámicaBacktracking

2

Algoritmos Voraces

Algoritmos Voraces

Algoritmos que implementan una búsqueda miopeToman decisiones de acuerdo a la información que tienen en el momento

Ventajas:Fáciles de implementarEficientes cuando funcionan de acuerdo al problema

Desventajas:Para problemas con un gran tamaño en el espacio de búsqueda, suelen ser ineficientes

3

Algoritmos Voraces

Suelen utilizarse en problemas de optimizaciónEjemplo

Considere que nos encontramos en un país con monedas de 100, 20, 10, 5 y 1Se debe de crear un algoritmo que permita dar el menor número de monedasSi se desea dar a una persona 289 pesos, ¿Qué monedas utilizarías?

Algoritmos Voraces

El algoritmo es “voraz” porque selecciona la mayor denominación disponible en el momento y que aun pueda generar una solución válida¿Qué problemas crees que se le pueden presentar al algoritmo si alguna de las denominaciones es limitada en cantidad?¿Siempre obtendrá la mejor solución? ¿Bajo que condiciones el algoritmo voraz no funcionará?

4

Ejemplo Camino Mínimo

Considere que se va a elaborar un programa que dado un conjunto de ciudades unidas entre si a través de cierto conjunto de carreteras, determine una ruta que permita visitar a todas las ciudades

Mapa

5

Ejemplo Camino Mínimo

El problema de visitar a todas las ciudades se relaciona con el problema de construir un “arbol de recubrimiento mínimo”¿Cómo atacarlo a través de la técnica de algoritmos voraces?

Árbol de Recubrimiento Mínimo

Posibles solucionesComenzar con un conjunto vacío T y seleccionar en cada etapa la arista más barata no considerada previamenteSeleccionar un nodo y construir un árbol a partir de él

¿Qué técnica, desde tu perspectiva es la mejor? ¿Cuál si funcionará?

6

Algoritmo de Kruskal

El algoritmo parte de un subconjunto vacío T de aristasSe van construyendo componentes conexas, a las cuales se les añade un arco que represente la mínima expansión al componente conexoSi una arista une a dos componentes conexas distintas, se añade a T, en caso contrario, se rechazaAl finalizar el algoritmo, sólo queda un componente conexo

Algoritmo de Kruskal

7

Algoritmo de Kruskal

Algoritmo de Kruskal

¿Qué problemas identificas en este algoritmo?¿En qué tipo de problemas aplicarías el algoritmo de Kruskal?

8

Algoritmo de PRIM

En el algoritmo de Kruskal, los árboles crecen de forma arbitraria NO se tiene un estado inicial predeterminadoUna alternativa a este algoritmo es PRIM, en cual parte de una raíz seleccionada arbitrariamente

En cada expansión, se añade una nueva rama al árbol ya construido

Algoritmo de PRIM

function PRIM (G= (V,A))T ∅B {v ∈ V} (seleccionado arbitrariamente)Mientras B ≠ V hacer

Buscar e = {u,v}, e ∈ A: u ∈ B, v ∈ V \ BT T ∪ {e}B B ∪ {v}

Regresar T

9

Algoritmo de PRIM

Para el ejemplo anterior y partiendo del vértice “1”, ¿Cuál sería el resultado?¿Cómo se implementaría en una computadora?

El problema de Caminos Mínimos

Otro problema clásico en grafos consiste en determinar, dado un origen, el camino mínimo que va desde un origen predefinido hasta cualquier nodo dentro del grafo

Entrada: G = (V, A)V = {v1, …, vn}, A ⊆ {(vi, vj): vi, vj ∈ V, vi ≠ vj}∀ e = (vi, vj) ∈ A: w(e) representa un peso positivo asociado al arco (vi, vj)Salida: D (matriz de distancias) y P (matriz de caminos)

10

Algoritmo de Dijkstra

El problema se puede resolver a través de un algoritmo conocido como DijkstraEl algoritmo utiliza las siguientes variables:

v1: nodo origenS: conjunto de vértices ya seleccionadosC = V \ S (conjunto de vértices no seleccionados)D: matriz que almacena el costo entre el nodo origen y cada uno de los vértices en GL: matriz de pesos entre todas las aristas dirigidas

Algoritmo de Dijkstra

function Dijkstra(L[][])C {1, 2, 3, …, n}D[1] 0, P[1] {1}Para i = 2 hasta n hacer

D[i] ∞, P[i] {}Mientras |C| > 1 hacer

v min{D[u]: u ∈ C}C C \ {v}para cada w ∈ C hacer

si D[w]>D[v]+L[v,w] entonces D[w] D[v]+L[v,w]P[w] P[v]∪{w}

11

Ejemplo 1

1

25

4 3

10 50

10

100 30

20

50

5

Ejemplo 2

12

Divide y Vencerás

Divide y Vencerás

Técnica que consiste en dividir un problema en subproblemas más pequeños, para combinar los resultados parciales para obtener el resultado final

Más sencillo resolver casos más pequeñosPara mayor eficiencia, se busca que los subproblemas generados tengan una dimensión similarSe debe verificar que la solución del problema original puede obtenerse a través de soluciones parciales

13

Multiplicación de Números Grandes

El algoritmo clásico de multiplicación realiza n2 multplicaciones y “n” sumas para 2 números con “n” cifras

98112343924

29431962981

1210554

Estrategia

Disminuir el número de multiplicaciones dividiendo los números a multiplicar

A) Por simplicidad se considera que los multiplicandos tienen la misma dimensión

En el ejemplo, 981 0981, 1234 1234

B) Se parten por mitades a ambos operandos0981: w = 09, x = 81 = 102w + x1234: y = 12, z = 34 = 102y + z

14

Estrategia

C) Se realiza la multiplicación con respecto a la descomposición de los números

981 * 1234 = (102w + x) (102y + z)= 104 wy + 102 (wz + xy) + xz= 1080000 + 127800 + 2754 = 1210554

En esta operación, las operaciones “wz” y “xy” se puede reescribir como:

r = (w + x) (y + z) = wy + (wz + xy) + xz

Estrategia

De lo anterior se procede de la siguiente manera:

p = wy = 09 * 12 = 108q = xz = 81 * 34 = 2754r = (w + x) (y + z) = 90 * 46 = 4140

Por tanto.981 * 1234 = 104p + 102(r – p – q) + q

= 1080000+127800+2754 = 1210554

15

Observaciones

Para este ejemplo, se sustituyó una multiplicación por 4 sumas¿Cuál es el ahorro real que se logra con esta forma alternativa de multiplicar números?

Forma tradicional: h(n) = cn2

Técnica divide y vencerásSi g(n) representa el costo de realizar las operaciones sin tomar en cuenta las tres multiplicaciones y considerando que las multiplicaciones se realizan de forma tradicional, tenemos que:3 h(n/2)+g(n) = 3c(n/2)2+g(n) = ¾ cn2+g(n) = 3/4h(n)+g(n)

Observaciones

Es evidente que ambos son algoritmos cuadráticos, ya que estamos considerando que el orden de las multiplicaciones de los números mitad se realizan de forma tradicional (orden cuadrático)Sin embargo, si los números mitad son suficientemente grandes, se puede aplicar de forma recursiva la técnica ya analizada

16

Observaciones

Por tanto, para saber la complejidad del algoritmo original, se llega a una recurrencia de la forma

t(n) = 3t(n/2) + g(n)Notemos que g(n) es una expresión que representa corrimientos y sumas entre números con “n” digitos, por lo que O(g(n)) = n

t(n) = 3t(n/2) + n

Complejidad Multiplicación de NúmerosDivide y Vencerás

De lo anterior se concluye queΘ(t(n)) = nlog 3 para n potencias de 2

log2 3 ≈ 1.585 < 2, por lo que la mejora con respecto al algoritmo tradicional de multiplicación para números grandes es significativo

17

Comentario

La pregunta clave en cualquier algoritmo de divide y vencerás es

¿Cuándo dejar de dividir los casos?Esta pregunta se debe de responder, ya que para casos pequeños, la técnica de divide y vencerás será sensiblemente más lenta que el algoritmo tradicional

Cuestión de Análisis

¿Cómo multiplicar números impares?¿Qué sucede si la longitud de los números a multiplicar es diferente?

Se puede demostrar que si u y v son los números a multiplicar con dimensión m y n respectivamente, el algoritmo divide y vencerás será más lento que el algoritmo clásico si m < n1/2

18

Búsqueda Binaria

Problema en el que, dado un arreglo ordenado de forma ascendente T con “n” elementos, verificar si existe un número “x” en el arreglo, regresando la posición en el arreglo si es que existeAlgoritmo tradicional: búsqueda secuencial

Costo: (n)

function secuential(int T[], int x){ para i 1 hasta n hacer

si T[i] ≥ n entonces devolver idevolver i+1;

}

Búsqueda Binaria

Estrategia: “dividir” al arreglo T por la mitad y verificar si x se encuentra en la parte superior o inferior del arreglo.

El algoritmo se ejecuta de forma recursiva buscando x ahora en cualquiera de las mitades del arreglo hasta encontrarlo o bien, hasta que el arreglo en el cual se busca sea vacío

19

Búsqueda Binaria

function busquedabin(T[], x){ si n = 0 o x > T[n] entonces devolver n+1;

devolver binrec(T, 1, n, x);}

function binrec(T[], int i, int j, x)}{ si i = j entonces

si T[i] = x regresar ien caso contrario, regresar -1

k int((i + j) / 2)si x ≤ T[k] entonces regresar binrec(T, i, k, x)regresar binrec(T, k+1, j, x)

}

Análisis

Notemos que en cada llamado recursivo, el algoritmo procesará la mitad del arreglo que se procesa en la iteración, por lo que:

T(n) = T(n/2) + g(n)Notemos que g(n) representa el costo de realizar la operación y división. Por simplicidad, consideramos que tiene un costo constante (para números muy grandes NO es constante)Por tanto, concluimos que T(n) ∈ Θ(log2 n)

20

Problema de Ordenar un Arreglo

Otro problema clásico dentro de la estrategia divide y vencerás es ordenar un arregloExisten varias técnicas que implementan la técnica divide y vencerás

Por Fusión (mergesort)Ordenación rápida (quicksort)

Ordenación por Fusión

Al igual que todas las técnicas de divide y vencerás, se divide el arreglo en dos partes y se procesa cada parte de forma recursiva, hasta que su dimensión es pequeña para utilizar un algoritmo básico de ordenaciónAl finalizar, se requiere de un algoritmo eficiente que fusione los arreglos ordenados

21

Ordenación por Fusión

procedimiento fusionar(U[m+1], V[n+1], T[m+n]){ i, j 1

U[m+1] ∞V[n+1] ∞para k 1 hasta m+n hacer

si U[i] < V[j]entonces T[k] U[i], i i + 1si no T[k] V[j], j j + 1

}

Ordenación por Fusión

procedimiento ordenarporfusion(T[n]){ si n es suficientemente pequeño

entonces ordenarporinsertar(T)en caso contrario

U[1, …, n/2] T[1, …, n/2]V[1, …, n/2] T[n/2 + 1, …, n]ordenarporfusion(U)ordenarporfusion(V)fusionar(U,V,T)

}

22

Análisis

Notemos que:Separar la matriz T en dos partes requiere un tiempo linealLa función fusionar también requiere un tiempo linealSea g(n) ∈ Θ(n)

Por tanto, la complejidad de la función queda definida como:

T(n) = 2T(n/2)) + g(n)De lo anterior se concluye que

T(n) ∈ Θ(n log2 n)

Ordenación Rápida

Este algoritmo parte de la premisa que se desea ordenar un arreglo en el cual los números se encuentran aleatoriamentedesordenadosDivide al arreglo en dos partes, ubicando un “pivote” que definirá como se parte al arreglo (no necesariamente es la mitad del arreglo)

23

Ordenación Rápida

Ordenación Rápida

Si el arreglo inicial se encontraba aleatoriamente desordenado, entonces el punto de partición tenderá a estar cerca de la mitad del arreglo. En caso contrario el algoritmo se vuelve ineficiente

24

Análisis

“partition” recorre de forma completa al arreglo que se procesa, por tanto, tiene un costo lineal con respecto a la dimensión del arreglo procesado“partition” regresa un valor “q” que siempre se encuentra entre 1 y n, donde cada valor tiene la misma probabilidad de aparecer (1/n)Por tanto, el costo de ejecutar quicksort es:

∑=

−+−+=n

intqtng

nnt

1

))1()1()((1)(

Análisis

Es evidente que esta expresión no se puede resolver de forma directa con las técnicas de solución de recurrencias vistas hasta el momentoSin embargo, considerando una distribución aleatoria de los números, el valor de “q” tenderá a ser un valor central, acercandose a n/2, lo que deja ver que para un caso promedio, quicksort tenderá a comportarse como Θ(n log2 n)

25

Multiplicación de Matrices

Sean A, B dos matrices de nxn y sea C la matriz productoPara calcular cada elemento C[i,j], el algoritmo clásico utiliza la expresión:

COSTO: Considerando que las sumas y productos son operaciones elementales, entonces calcular cada entrada tiene un costo Θ(n)Dado que hay que calcular n2 entradas, el costo total de la multiplicación es de Θ(n3)

∑=

=n

kjkBkiAjiC

1],[*],[],[

Algoritmo de Strassen

Considere dos matrices A y B

Sean las operaciones siguientes:m1 = (a2,1+ a2,2 – a1,1) (b2,2 – b1,2 + b1,1)m2 = a11 b1,1m3 = a1,2 b2,1m4 = (a1,1 – a2,1) (b2,2 – b1,2)m5 = (a2,1 + a22) (b1,2 – b1,1)m6 = (a1,2 – a2,1 + a,1,1 – a2,2) (b2,2)m7 = a2,2 (b1,1 + b2,2 – b1,2 – b2,1)

=

=

2,21,2

2,11,1

2,21,2

2,11,1 ,bbbb

Baaaa

A

26

Algoritmo de Strassen

A partir de estas operaciones, la multiplicación de las matrices se define como:

+++−++++++

=55217421

652132

mmmmmmmmmmmmmm

C

Algoritmo de Strassen

En este ejemplo, la multiplicación se efectúa a través de:

14 sumas y 7 multiplicacionesEl algoritmo tradicional utiliza 4 sumas y 8 multiplicacionesEn primera instancia el algoritmo no representa ningún beneficio sustancial

27

Algoritmo de Strassen

Sin embargo, si se sustituye cada entrada de las matrices por una matriz de n x n, entonces es capaz de multiplicar 2 matrices de 2n x 2n con

Siete multiplicaciones de matrices n x nCierto número de sumas y restas de matrices n x n

De lo anterior, se puede inferir que esta técnica tendrá el siguiente costo:

T(n) = 7T(n/2) + g(n), donde g(n) ∈ Θ(n2)

Algoritmo de Strassen

Resolviendo la recurrencia, se determina que:

T(n) ∈ Θ (nlog 7) ≈ Θ (n2.81) < Θ (n3)

28

Programación Dinámica

Programación Dinámica

La programación dinámica surge como respuesta para evitar la duplicidad de cálculos operaciones

Cuando se resuelve un problema a través de subproblemas, es probable que dichos subproblemas tengan operaciones comunes entre sí (no son independientes)La programación dinámica consiste en identificar y guardar los resultados de los cálculos comunes para su uso posterior

29

Ejemplo 1: Coeficiente Binomial

El coeficiente binomial se define de la siguiente forma:

La solución trivial a este problema es:

<<

−+

−−

==

=

nksi

kn

kn

nkksi

kn

01

11

,01

funcion C(n,k){ si k = 0 ó k = n entonces regresar 1

regresar C(n-1, k-1) + C(n-1,k)}

Ejemplo 1: Coeficiente Binomial

Es evidente que muchos valores C(i,j) se calculan varias vecesPor ejemplo, sea C(5,3) genera:

+

=

34

24

35

CCC

+

=

23

13

24

CCC

+

=

33

23

34

CCC

+

=

12

02

13

CCC

+

=

22

12

23

CCC

+

=

11

01

12

CCC

30

Ejemplo 1: Coeficiente Binomial

Ejemplo 2: Devolver Cambio

Dado un conjunto de “n” denominaciones d1, …, dn, se debe de dar una cantidad C con dichas denominaciones, minimizando el número de monedas a darPara analizar el problema desde la perspectiva de programación dinámica, asumamos:

De inicio que se tiene un suministro ilimitado de cada denominación

31

Ejemplo 2: Devolver Cambio

Para resolver el problema con programación dinámica, se considera una matriz C donde:

CnxN: una fila por cada denominación y una columna para las cantidades que van desde 0 hasta NC[i,j] representa el # mínimo de monedas para dar la cantidad “j”, utilizando monedas sólo de denominación menor o igual a “i”La solución general estará dada por C[n,N]

Ejemplo 2: Devolver Cambio

Asignación de valores de la matriz C:La matriz se llena fila a fila (arriba abajo) o columna a columna, (izquierda derecha)La 1a columna se rellena con 0’s (no se puede dar $0 con monedas de valor mayor o igual a uno)Para dar $j pesos con monedas de denominación menor o igual a “i”, C[i,j] se tienen dos opciones:

No utilizar monedas de denominación “di” (C[i,j]=C[i-1,j])Utilizar una moneda de denominación “di” más el número de monedas para dar el resto (C[i,j-di])

C[i,j] = min {C[i-1,j], 1 + C[i,j-di]}

32

Ejemplo 2: Devolver Cambio

Cantidad 0 1 2 3 4 5 6 7 8d1 = 1 0 1 2 3 4 5 6 7 8d2 = 4 0 1 2 3 1 2 3 4 2d3 = 6 0 1 2 3 1 2 1 2 2M

oned

as

NOTAS:• Si alguno de los elementos a comparar caen fuera de la matriz, se consideran

a estos valores como +∞• Si ambos elementos a comparar caen fuera de la tabla, se asigna +∞ a la

casilla correspondiente

Ejemplo 2: Devolver Cambio

¿Como determinamos que monedas se necesitan dar a partir de la matriz C?¿Qué pasa si el número de monedas que se suministran es limitado? ¿Cómo se podría mejorar el algoritmo para considerar este hecho?¿Qué tiempo requiere el algoritmo para ejecutarse?

33

Ejemplo 3: Algoritmo de Floyd

Considere un grafo dirigido G = (V, A), donde se desea calcular la distancia mínima entre cada par de nodos

V = {v1, …, vn}A ⊆ {(vi, vj): vi, vj ∈ V}L matriz de distancias, donde L[i,j] es la distancia entre el vértices vi y vj que son unidos por un arcoL[i,i] = 0, 1 ≤ i ≤ nL[i,j] = ∞ si (vi, vj) ∉ A

Ejemplo 3: Algoritmo de Floyd

El algoritmo de Floyd consta de los siguientes pasos:

Se construye una matriz D de dimensión nxn (por notación, Dk representa los valores de la matriz D en la k-ésima iteración)Se inicializa D0 LSe realizan n iteración, donde para la k-ésimaiteración Dk da la longitud más corta entre cualquier par de nodos, utilizando sólo los vértices v1, …, vk

34

Ejemplo 3: Algoritmo de Floyd

En cada iteración k, para todo par de nodos (vi, vj) se verifica si existe un camino entre viy vj pasando por vk y que sea menor al camino óptimo actual

Dk[i,j] = min{Dk-1[i,j], Dk-1[i,k] + Dk-1[k,j]}En este algoritmo se utiliza el principio de OPTIMALIDAD

Ejemplo 3: Algoritmo de Floyd

function Floyd(L[], int n){ D L;

para i 1 hasta n hacerpara j 1 hasta n hacer

P[i][j] {} //matriz para recuperar los caminospara k 1 hasta n hacer

para i 1 hasta n hacerpara j 1 hasta n hacer

si D[i][k] + D[k][j] < D[i][j]D[i][j] D[i][k] + D[k][j]P[i][j] P[i][j] ∪ {k}

regresar D, P;}

35

Ejemplo 3: Algoritmo de Floyd

∞∞

=

0520151503530515050

50

1D

∞∞

∞∞

==

051515030515050

50

0 LD

=

0520151503530515045

102050

3D

1

2

3

4

30

5 50

5

515

15

15

=

0520151503530515050202050

2D

=

0520151503530510020

101550

4D

Vuelta Atrás(Backtracking)

36

Introducción

Técnica ampliamente utilizada para resolver problemas a través de una búsqueda exhaustiva

Proporciona un método sistemático de generar todas las posibles solucionesA partir de una solución parcial de tamaño “k”, se genera una nueva solución de tamaño “k+1” (a partir de una expansión)

Si la solución “k+1” es válida el proceso de expansión continúaEn caso contrario, se abandona dicha solución y se regresa a la solución de dimensión “k” para expandir otra posible solución

Introducción

En el proceso de expansión:No se tienen reglas fijas para ejecutarla –dependiente del problemaEl número de combinaciones a explorar puede crecer de forma exponencial

En la práctica, es necesario implementar reglas que limiten el número de posibles expansionesHay que tener cuidado en el tipo de restricciones a implementar (costo para ejecutarla, preservar cualquier solución válida del problema)

37

Condiciones para Implementar Backtracking

La solución parcial Sk de un problema P se debe poder expresar como una tupla

Sk = <s1, s2, …, sk>Cada si en Sk representa un estado válido en P, donde S es una solución parcial de P con dimensión “k”A partir de sk se busca un conjunto E = {st: st es un estado válido de P y st no forma parte de Sk}Una expansión consiste en seleccionar un sx ∈ E:

Sk+1 = < s1, s2, …, sk, sx>E = E \ {sx}

Ejemplo: El problema de las 8 Reinas

Se dispone de un tablero de ajedrez (8x8). El problema consiste en colocar en el tablero 8 reinas de ajedrez, de tal forma que no se amenacen entre sí

38

Ejemplo: El problema de las 8 Reinas

Las soluciones se pueden representar a través del vector S = <r1, r2, r3, …, r8>

ri representa la columna donde la reina de lai-ésima fila será colocada en el tablero

S = <4,7,3,8,2,5,1,6>

Ejemplo: El problema de las 8 Reinas

Restricciones:Los valores de ri están restringidos al conjunto T = {1, 2, 3, 4, 5, 6, 7, 8}Dos reinas no pueden situarse en la misma columna y en la misma fila

Por la definición de S, no hay 2 reinas en la misma filaNo pueden haber dos xi en S con el mismo valor

Dos reinas no pueden situarse en la misma diagonal

Si (x,y) y (x’,y’) representan la posición de dos reinas, entonces |x – x’| ≠ |y – y’|

39

Ejemplo: El problema de las 8 Reinas

El problema de las Parejas Estables

Considere que se tienen “n” hombres y mujeres, donde expresan su preferencia hacia una persona del sexo opuesto a través de dos matrices M y H

Matriz M: preferencia de las mujeres hacia los hombresMatriz H: preferencia de los hombres hacia las mujeres

40

El Problema de las Parejas Estables

Las columnas de las matrices representan el orden de preferencia (de mayor a menor) de una persona por otra del sexo opuesto

MUJERESRosa Maria Petra RitaPepe Jose Pepe PedroPedro Juan Jose JoseJuan Pedro Juan PepeJose Pepe Pedro Juan

HOMBRESPepe Juan Pedro JoséRosa Rita Rita MariaMaria Petra Petra RosaPetra Rosa Rosa PetraRita Maria Maria Rita

M[i,j]: preferencia de la i-ésimaMujer por los hombres

H[i,j]: preferencia del i-ésimohombre por las mujeres

El Problema de las Parejas Estables

Diseñar un algoritmo que encuentre, si es que existe, una asociación entre mujeres y hombres, de tal forma que las parejas sean establesUna pareja (h, m) es estable si no se presenta lo siguiente:

Existe una mujer m’ (con pareja (h’, m’)) tal que el hombre h la prefiere sobre la mujer m y además, la mujer m’ prefiere a h sobre h’Existe un hombre h’’ (con pareja (h’’, m’’)) tal que la mujer m lo prefiere sobre el hombre h y además, el hombre h’’ prefiere a m sobre m’’

41

El Problema de las Parejas Estables

Para resolver el problema, se considera lo siguiente:

La solución se representa con un vector A[1…n], donde A[i] es el hombre asociado a la i-ésima mujer (o viceversa)

Se utiliza un vector auxiliar (Libre) que representa que persona aun no ha sido asociadaSe utilizan dos matrices, H y M, para expresar la preferenciaAntes de añadir un elemento al vector, se verifica que la persona este libre y que la asociación resulte en una pareja estable