Recientemente escribí un código que pensé que era muy ineficiente, pero como solo incluía unos pocos valores, lo acepté. Sin embargo, todavía estoy interesado en un mejor algoritmo para lo siguiente:
- Una lista de objetos X, a cada uno de ellos se le asigna un "peso"
- Resume los pesos
- Genera un número aleatorio de 0 a la suma
- Iterar a través de los objetos, restando su peso de la suma hasta que la suma no sea positiva
- Elimine el objeto de la lista y luego agréguelo al final de la nueva lista
Los ítems 2,4 y 5 toman n
tiempo, por lo que es un O(n^2)
algoritmo.
¿Se puede mejorar esto?
Como ejemplo de una combinación aleatoria ponderada, un elemento tiene una mayor probabilidad de estar al frente con un peso mayor.
Ejemplo (generaré números aleatorios para hacerlo real):
6 objetos con pesas 6,5,4,3,2,1; Suma es 21
Elegí 19: por lo 19-6-5-4-3-2 = -1
tanto, 2 va en la primera posición, los pesos ahora son 6,5,4,3,1; Suma es 19
Elegí 16: por lo 16-6-5-4-3 = -2
tanto, 3 va en la segunda posición, los pesos ahora son 6,5,4,1; Suma es 16
Elegí 3: por lo 3-6 = -3
tanto, 6 va en la tercera posición, los pesos ahora son 5,4,1; Suma es 10
Escogí 8: 8-5-4 = -1
entonces 4 va en la cuarta posición, los pesos ahora son 5,1; Suma es 6
Escogí 5: 5-5=0
entonces 5 va en la quinta posición, los pesos ahora son 1; Suma es 1
Elegí 1: por lo 1-1=0
tanto, 1 va en la última posición, no tengo más pesas, termino
fuente
Respuestas:
Esto se puede implementar al
O(n log(n))
usar un árbol.Primero, cree el árbol, manteniendo en cada nodo la suma acumulativa de todos los nodos descendientes a la derecha y a la izquierda de cada nodo.
Para muestrear un elemento, muestre recursivamente desde el nodo raíz, utilizando las sumas acumulativas para decidir si devuelve el nodo actual, un nodo desde la izquierda o un nodo desde la derecha. Cada vez que muestree un nodo, establezca su peso en cero y también actualice los nodos principales.
Esta es mi implementación en Python:
Uso:
weigthed_shuffle
es un generador, por lo que puede probar losk
elementos principales de manera eficiente. Si desea mezclar todo el conjunto, simplemente repita el proceso hasta el agotamiento (utilizando lalist
función).ACTUALIZAR:
El muestreo aleatorio ponderado (2005; Efraimidis, Spirakis) proporciona un algoritmo muy elegante para esto. La implementación es súper simple y también se ejecuta en
O(n log(n))
:fuente
EDITAR: Esta respuesta no interpreta los pesos de la manera que se esperaría. Es decir, un artículo con peso 2 no tiene el doble de probabilidades de ser el primero que uno con peso 1.
Una forma de barajar una lista es asignar números aleatorios a cada elemento de la lista y ordenarlos por esos números. Podemos extender esa idea, solo tenemos que elegir números aleatorios ponderados. Por ejemplo, podrías usar
random() * weight
. Diferentes opciones producirán diferentes distribuciones.En algo como Python, esto debería ser tan simple como:
Tenga cuidado de no evaluar las claves más de una vez, ya que terminarán con valores diferentes.
fuente
max*min = min*max
, y por lo tanto, es posible cualquier permutación, pero algunas son mucho más probables (especialmente si los pesos no están distribuidos uniformemente)Primero, trabajemos desde que el peso de un elemento dado en la lista que se va a ordenar es constante. No va a cambiar entre iteraciones. Si lo hace, entonces ... bueno, ese es un problema mayor.
Por ejemplo, usemos una baraja de cartas donde queremos pesar las cartas de la cara al frente.
weight(card) = card.rank
. Resumiendo estos, si no sabemos la distribución de los pesos es de hecho O (n) una vez.Estos elementos se almacenan en una estructura ordenada, como una modificación en una lista de omisión indexable, de modo que se pueda acceder a todos los índices de los niveles desde un nodo dado:
Sin embargo, en este caso, cada nodo también 'ocupa' tanto espacio como su peso.
Ahora, al buscar una tarjeta en esta lista, se puede acceder a su posición en la lista en el tiempo O (log n) y eliminarla de las listas asociadas en el tiempo O (1). Ok, podría no ser O (1), podría ser O (log log n) tiempo (tendría que pensar en esto mucho más). Eliminar el sexto nodo en el ejemplo anterior implicaría actualizar los cuatro niveles, y esos cuatro niveles son independientes de la cantidad de elementos que hay en la lista (dependiendo de cómo implemente los niveles).
Como el peso de un elemento es constante, uno puede hacerlo
sum -= weight(removed)
sin tener que atravesar la estructura nuevamente.Y por lo tanto, tiene un costo único de O (n) y un valor de búsqueda de O (log n) y un costo de eliminación de la lista de O (1). Esto se convierte en O (n) + n * O (log n) + n * O (1) que le proporciona un rendimiento general de O (n log n).
Veamos esto con tarjetas, porque eso es lo que usé anteriormente.
Este es un mazo realmente pequeño con solo 4 cartas. Debería ser fácil ver cómo se puede extender esto. Con 52 cartas, una estructura ideal tendría 6 niveles (log 2 (52) ~ = 6), aunque si profundiza en las listas de omisión, incluso eso podría reducirse a un número menor.
La suma de todos los pesos es 10. Entonces obtienes un número aleatorio de [1 .. 10) y es 4 Recorres la lista de saltos para encontrar el elemento que está en el techo (4). Como 4 es menor que 10, pasas del nivel superior al segundo nivel. Cuatro es mayor que 3, así que ahora estamos en el 2 de los diamantes. 4 es menos de 3 + 7, por lo que nos movemos hacia el nivel inferior y 4 es menos de 3 + 3, por lo que tenemos un 3 de diamantes.
Después de eliminar los 3 diamantes de la estructura, la estructura ahora se ve así:
Notará que los nodos ocupan una cantidad de 'espacio' proporcional a su peso en la estructura. Esto permite la selección ponderada.
Como esto se aproxima a un árbol binario equilibrado, la búsqueda en este no necesita caminar por la capa inferior (que sería O (n)) y, en cambio, ir desde la parte superior le permite saltar rápidamente la estructura para encontrar lo que está buscando para.
Gran parte de esto podría hacerse con algún tipo de árbol equilibrado. El problema es que el reequilibrio de la estructura cuando se elimina un nodo se vuelve confuso, ya que esta no es una estructura de árbol clásica y la limpieza para recordar que el 4 de los diamantes ahora se mueve de las posiciones [6 7 8 9] a [3 4 5 6] puede costar más que los beneficios de la estructura del árbol.
Sin embargo, aunque la lista de omisión se aproxima a un árbol binario en su capacidad de omitir la lista en tiempo O (log n), tiene la simplicidad de trabajar con una lista vinculada.
Esto no quiere decir que sea fácil hacer todo esto (aún necesita mantener pestañas en todos los enlaces que necesita modificar cuando elimina un elemento), pero significa solo actualizar todos los niveles que tenga y sus enlaces. que todo a la derecha en la estructura de árbol adecuada.
fuente