Arrojando a la gente más gorda de un avión sobrecargado.


Digamos que tienes un avión, y está bajo en combustible. A menos que el avión caiga 3000 libras de peso de pasajero, no podrá llegar al próximo aeropuerto. Para salvar el máximo número de vidas, nos gustaría echar a las personas más pesadas del avión primero.

Y oh sí, hay millones de personas en el avión, y nos gustaría un algoritmo óptimo para encontrar a los pasajeros más pesados, sin necesariamente ordenar toda la lista.

Este es un problema de proxy para algo que estoy tratando de codificar en C++. Me gustaría hacer un "partial_sort" en el manifiesto de pasajeros por peso, pero no se cuantos elementos voy a necesitar. Podría implementar mi propio algoritmo "partial_sort" ("partial_sort_accumulate_until"), pero me pregunto si hay alguna manera más fácil de hacer esto usando STL estándar.

Author: Louis Rhys, 2011-10-13

12 answers

Una forma sería usar un montón min (std::priority_queue en C++). Así es como lo harías, suponiendo que tuvieras una clase MinHeap. (Sí, mi ejemplo está en C#. Creo que entiendes la idea.)

int targetTotal = 3000;
int totalWeight = 0;
// this creates an empty heap!
var myHeap = new MinHeap<Passenger>(/* need comparer here to order by weight */);
foreach (var pass in passengers)
{
    if (totalWeight < targetTotal)
    {
        // unconditionally add this passenger
        myHeap.Add(pass);
        totalWeight += pass.Weight;
    }
    else if (pass.Weight > myHeap.Peek().Weight)
    {
        // If this passenger is heavier than the lightest
        // passenger already on the heap,
        // then remove the lightest passenger and add this one
        var oldPass = myHeap.RemoveFirst();
        totalWeight -= oldPass.Weight;
        myHeap.Add(pass);
        totalWeight += pass.Weight;
    }
}

// At this point, the heaviest people are on the heap,
// but there might be too many of them.
// Remove the lighter people until we have the minimum necessary
while ((totalWeight - myHeap.Peek().Weight) > targetTotal)
{
    var oldPass = myHeap.RemoveFirst();
    totalWeight -= oldPass.Weight; 
}
// The heap now contains the passengers who will be thrown overboard.

De acuerdo con las referencias estándar, el tiempo de funcionamiento debe ser proporcional a n log k, donde n es el número de pasajeros y k es el número máximo de artículos en el montón. Si asumimos que el peso de los pasajeros normalmente será de 100 libras o más, entonces es poco probable que el montón contendrá más de 30 artículos en cualquier momento.

El peor caso sería si los pasajeros se presentan en orden desde el peso más bajo hasta el más alto. Eso requeriría que cada pasajero sea agregado al montón, y cada pasajero sea removido del montón. Aún así, con un millón de pasajeros y suponiendo que el más ligero pesa 100 libras, el n log k funciona a un número razonablemente pequeño.

Si obtienes los pesos de los pasajeros al azar, el rendimiento es mucho mejor. Yo uso algo bastante como esto para un motor de recomendación (selecciono los 200 elementos principales de una lista de varios millones). Normalmente termino con solo 50,000 o 70,000 artículos realmente agregados al montón.

Sospecho que verá algo bastante similar: la mayoría de sus candidatos serán rechazados porque son más ligeros que la persona más ligera que ya está en el montón. Y Peek es una operación O(1).

Para obtener más información sobre el rendimiento de heap select y quick select, consulte Cuando la teoría se encuentra con la práctica. Versión corta: si estás seleccionando menos del 1% del número total de artículos, heap select es un claro ganador sobre quick select. Más del 1%, luego use quick select o una variante como Introselect.

 101
Author: Jim Mischel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-20 20:11:59

Esto no ayudará para su problema de proxy, sin embargo:

Para que 1,000,000 pasajeros bajen 3000 libras de peso, cada pasajero debe perder (3000/1000000) = 0.003 libras por persona. Eso podría lograrse desechando cada camisa, o zapatos, o probablemente incluso recortes de uñas, salvando a todos. Esto supone una recolección eficiente y el deshecho antes de que la pérdida de peso necesaria aumentara a medida que el avión usaba más combustible.

En realidad, no permiten cortauñas a bordo nunca más, así que eso está fuera.

 116
Author: aportr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-19 01:41:47

A continuación se muestra una implementación bastante simple de la solución directa. No creo que haya una forma más rápida que sea 100% correcta.

size_t total = 0;
std::set<passenger> dead;
for ( auto p : passengers ) {
    if (dead.empty()) {
       dead.insert(p);
       total += p.weight;
       continue;
    }
    if (total < threshold || p.weight > dead.begin()->weight)
    {
        dead.insert(p);
        total += p.weight;
        while (total > threshold)
        {
            if (total - dead.begin()->weight < threshold)
                break;
            total -= dead.begin()->weight;
            dead.erase(dead.begin());
        }
    }
 }

Esto funciona llenando el conjunto de "personas muertas" hasta que alcanza el umbral. Una vez que se alcanza el umbral, seguimos revisando la lista de pasajeros tratando de encontrar cualquiera que sea más pesado que la persona muerta más ligera. Cuando hemos encontrado uno, los añadimos a la lista y luego comenzamos a "Guardar" a las personas más ligeras de la lista hasta que no podamos guarda más.

En el peor de los casos, esto se llevará a cabo aproximadamente lo mismo que una especie de toda la lista. Pero en el mejor de los casos (la "lista muerta" se llena correctamente con las primeras X personas) funcionará O(n).

 42
Author: SoapBox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-14 23:41:12

Suponiendo que todos los pasajeros cooperarán: red de clasificación paralela. (véase también este )

Aquí hay una demostración en vivo

Actualización: Video alternativo (saltar a 1:00)

Pedir a pares de personas que comparen-intercambien - no puede ser más rápido que esto.

 31
Author: Lior Kogan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-11 06:45:10

@Blastfurnace estaba en el camino correcto. Utilice quickselect donde los pivotes son umbrales de peso. Cada partición divide un conjunto de personas en conjuntos y devuelve el peso total de cada conjunto de personas. Continúa rompiendo el balde apropiado hasta que sus baldes correspondientes a las personas de mayor peso superen las 3000 libras, y su balde más bajo que está en ese conjunto tenga 1 persona (es decir, no se puede dividir más.)

Este algoritmo es tiempo lineal amortizado, pero el peor caso cuadrático. Creo que es el único algoritmo de tiempo lineal.


Aquí hay una solución de Python que ilustra este algoritmo:

#!/usr/bin/env python
import math
import numpy as np
import random

OVERWEIGHT = 3000.0
in_trouble = [math.floor(x * 10) / 10
              for x in np.random.standard_gamma(16.0, 100) * 8.0]
dead = []
spared = []

dead_weight = 0.0

while in_trouble:
    m = np.median(list(set(random.sample(in_trouble, min(len(in_trouble), 5)))))
    print("Partitioning with pivot:", m)
    lighter_partition = []
    heavier_partition = []
    heavier_partition_weight = 0.0
    in_trouble_is_indivisible = True
    for p in in_trouble:
        if p < m:
            lighter_partition.append(p)
        else:
            heavier_partition.append(p)
            heavier_partition_weight += p
        if p != m:
            in_trouble_is_indivisible = False
    if heavier_partition_weight + dead_weight >= OVERWEIGHT and not in_trouble_is_indivisible:
        spared += lighter_partition
        in_trouble = heavier_partition
    else:
        dead += heavier_partition
        dead_weight += heavier_partition_weight
        in_trouble = lighter_partition

print("weight of dead people: {}; spared people: {}".format(
    dead_weight, sum(spared)))
print("Dead: ", dead)
print("Spared: ", spared)

Salida:

Partitioning with pivot: 121.2
Partitioning with pivot: 158.9
Partitioning with pivot: 168.8
Partitioning with pivot: 161.5
Partitioning with pivot: 159.7
Partitioning with pivot: 158.9
weight of dead people: 3051.7; spared people: 9551.7
Dead:  [179.1, 182.5, 179.2, 171.6, 169.9, 179.9, 168.8, 172.2, 169.9, 179.6, 164.4, 164.8, 161.5, 163.1, 165.7, 160.9, 159.7, 158.9]
Spared:  [82.2, 91.9, 94.7, 116.5, 108.2, 78.9, 83.1, 114.6, 87.7, 103.0, 106.0, 102.3, 104.9, 117.0, 96.7, 109.2, 98.0, 108.4, 99.0, 96.8, 90.7, 79.4, 101.7, 119.3, 87.2, 114.7, 90.0, 84.7, 83.5, 84.7, 111.0, 118.1, 112.1, 92.5, 100.9, 114.1, 114.7, 114.1, 113.7, 99.4, 79.3, 100.1, 82.6, 108.9, 103.5, 89.5, 121.8, 156.1, 121.4, 130.3, 157.4, 138.9, 143.0, 145.1, 125.1, 138.5, 143.8, 146.8, 140.1, 136.9, 123.1, 140.2, 153.6, 138.6, 146.5, 143.6, 130.8, 155.7, 128.9, 143.8, 124.0, 134.0, 145.0, 136.0, 121.2, 133.4, 144.0, 126.3, 127.0, 148.3, 144.9, 128.1]
 21
Author: Neil G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-13 20:18:36

Suponiendo que, al igual que los pesos de las personas, usted tiene una buena idea de lo que los valores máximo y mínimo son probables utilizar un tipo radix para ordenarlos en O(n). A continuación, simplemente trabajar desde el extremo más pesado de la lista hacia el más ligero. Tiempo total de funcionamiento: O (n). Desafortunadamente, no hay una implementación de una ordenación radix en el STL, pero es bastante sencillo de escribir.

 11
Author: Keith Irwin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-12 21:39:35

¿Por qué no usa un quicksort parcial con una regla de aborto diferente a "ordenado"? Puede ejecutarlo y luego usar solo la mitad superior y continuar hasta que el peso dentro de esta mitad superior no contenga el peso que al menos debe desecharse más, luego retroceda un paso en la recursión y clasifique la lista. Después de que usted puede comenzar a tirar a la gente de la parte superior de esa lista ordenada.

 6
Author: Sim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-12 21:26:12

Clasificación Masiva de Torneos Paralelos: -

Suponiendo un estándar de tres asientos a cada lado de la ailse: -

  1. Pida a los pasajeros en el asiento de la ventana que se muevan al asiento del medio si pesan más que la persona en el asiento de la ventana.

  2. Pida a los pasajeros en el asiento del medio que intercambien con el pasajero en el asiento del pasillo si son más pesados.

  3. Pídale al pasajero en el asiento del pasillo izquierdo que intercambie con el pasajero en el asiento del pasillo derecho más pesado.

  4. Burbuja ordenar a los pasajeros en el asiento del pasillo derecho. (Toma n pasos para n filas). -- pida a los pasajeros en el asiento del pasillo derecho que intercambien con la persona en frente n -1 veces.

5 Patéalos por la puerta hasta que alcances 3000 libras.

3 pasos + n pasos más 30 pasos si tienes una carga de pasajeros realmente delgada.

Para un avión de dos pasillos the las instrucciones son más complejas pero el rendimiento es más o menos el mismo.

 6
Author: James Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-13 06:43:36

Probablemente usaría std::nth_element para dividir las 20 personas más pesadas en tiempo lineal. Luego use un método más complejo para encontrar y eliminar el más pesado de los pesados.

 4
Author: Blastfurnace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-12 22:02:42

Usted podría hacer una pasada sobre la lista para obtener la media y la desviación estándar, a continuación, utilizar que para aproximar el número de personas que tienen que ir. Utilice partial_sort para generar la lista basada en ese número. Si la conjetura fue baja, use partial_sort de nuevo en el resto con una nueva conjetura.

 3
Author: Mark Ransom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-12 21:25:59

@James tiene la respuesta en los comentarios: un std::priority_queue si usted puede utilizar cualquier recipiente, o una combinación de std::make_heap y std::pop_heap (y std::push_heap) si quieres usar algo como un std::vector.

 3
Author: Max Lybbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-12 21:36:50

Aquí hay una solución basada en heap usando el módulo heapq integrado en Python. Está en Python, por lo que no responde a la pregunta original, pero es más limpio (en mi humilde opinión) que la otra solución de Python publicada.

import itertools, heapq

# Test data
from collections import namedtuple

Passenger = namedtuple("Passenger", "name seat weight")

passengers = [Passenger(*p) for p in (
    ("Alpha", "1A", 200),
    ("Bravo", "2B", 800),
    ("Charlie", "3C", 400),
    ("Delta", "4A", 300),
    ("Echo", "5B", 100),
    ("Foxtrot", "6F", 100),
    ("Golf", "7E", 200),
    ("Hotel", "8D", 250),
    ("India", "8D", 250),
    ("Juliet", "9D", 450),
    ("Kilo", "10D", 125),
    ("Lima", "11E", 110),
    )]

# Find the heaviest passengers, so long as their
# total weight does not exceeed 3000

to_toss = []
total_weight = 0.0

for passenger in passengers:
    weight = passenger.weight
    total_weight += weight
    heapq.heappush(to_toss, (weight, passenger))

    while total_weight - to_toss[0][0] >= 3000:
        weight, repreived_passenger = heapq.heappop(to_toss)
        total_weight -= weight


if total_weight < 3000:
    # Not enough people!
    raise Exception("We're all going to die!")

# List the ones to toss. (Order doesn't matter.)

print "We can get rid of", total_weight, "pounds"
for weight, passenger in to_toss:
    print "Toss {p.name!r} in seat {p.seat} (weighs {p.weight} pounds)".format(p=passenger)

Si k = el número de pasajeros a lanzar y N = el número de pasajeros, entonces el mejor caso para este algoritmo es O(N) y el peor caso para este algoritmo es Nlog(N). El peor caso ocurre si k está cerca de N durante mucho tiempo. He aquí un ejemplo del peor reparto:

weights = [2500] + [1/(2**n+0.0) for n in range(100000)] + [3000]

Sin embargo, en este caso (tirar a la gente del avión (con un paracaídas, supongo)) entonces k debe ser menos de 3000, que es

 2
Author: Andrew Dalke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-19 10:34:03