過負荷の飛行機から太っている人々を捨てる。


200

飛行機があり、燃料が少ないとしましょう。飛行機が乗客の体重の3000ポンドを落とさない限り、次の空港に到着することはできません。最大の命を救うために、一番重い人を飛行機から降ろしたいと思います。

そして、そうそう、飛行機には何百万人もの人がいるので、リスト全体をソートする必要はなく、最も重い乗客を見つけるための最適なアルゴリズムが欲しいのです。

これは、C ++でコーディングしようとしているもののプロキシの問題です。乗客のマニフェストで「partial_sort」を重量で実行したいのですが、必要な要素の数がわかりません。独自の "partial_sort"アルゴリズム( "partial_sort_accumulate_until")を実装することもできますが、標準のSTLを使用してこれを行う簡単な方法があるかどうか疑問に思っています。


5
人間にたとえれば、体重がXを超える人、たとえば120 kgを倒すことから始めることができます。
RedX

132
すべての乗客がアルゴリズムのいずれかのステップに協力しますか?
Lior Kogan、

34
このようなトピックが私がITを愛する理由です。
Markus

14
これがどの航空会社のものか尋ねることはできますか?私はホリデーシーズンのにのみ一緒に飛ぶようにしたいと思います。
jp2code、2011年

24
適切な機器(はかりが内蔵されたエジェクターシートなど)では、乗客の協力は必要ありません。
ジムフレッド

回答:


102

1つの方法は、最小ヒープstd::priority_queueC ++)を使用することです。あなたが持っていたと仮定して、あなたがそれを行う方法は次のとおりですMinHeapクラス。(はい、私の例はC#です。アイデアは理解できたと思います。)

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.

標準の参考文献によると、実行時間はに比例するはずですn log knで、は乗客の数とkあります。数であり、はヒープ上のアイテムの最大数です。乗客の重量が通常100ポンド以上であると想定すると、ヒープに常に30を超えるアイテムが含まれる可能性は低くなります。

最悪のケースは、乗客が最低体重から最高体重の順に提示される場合です。そのため、すべての乗客をヒープに追加し、すべての乗客をヒープから削除する必要があります。それでも、100万人の乗客がいて、最軽量の重量が100ポンドであると仮定すると、n log k作業はかなり少ない数になります。

乗客の体重をランダムに取得すると、パフォーマンスが大幅に向上します。私はこのようなものをレコメンデーションエンジンに使用しています(数百万のリストから上位200のアイテムを選択しています)。通常、実際にヒープに追加されるアイテムは50,000または70,000だけです。

似たようなものが表示されるのではないかと思います。候補者の大多数は、すでにヒープ上にいる最も軽い人物よりも軽いため、拒否されます。そしてPeekO(1)操作。

ヒープ選択とクイック選択のパフォーマンスの詳細については、「理論と実践が出会うとき」を参照してください。ショートバージョン:アイテムの総数の1%未満を選択している場合、ヒープ選択はクイック選択よりも明らかに勝っています。1%以上の場合は、クイック選択またはIntroselectなどのバリアントを使用します。


1
SoapBoxはより速い回答を投稿しました。
Mooing Duck、2011

7
私の読書では、SoapBoxの答えはジムミッシェルの答えと道徳的に同等です。SoapBoxはコードをC ++で記述したため、MinHeapと同じlog(N)追加時間を持つstd :: setを使用します。
IvyMike、2011年

1
線形時間解があります。追加します。
ニールG

2
最小ヒープのためのSTLのクラスがあります:std::priority_queue
bdonlan

3
@MooingDuck:おそらくあなたは誤解しました。SoapBoxのコードが空のセットを作成するのと同じように、私のコードは空のヒープを作成します。主な違いは、私が見ているように、彼のコードはより高い重量のアイテムが追加されると超過重量のセットをトリムするのに対し、私のものは超過を維持し、最後にそれをトリムすることです。彼のセットは、より重い人を見つけるリストを移動するにつれて、サイズが小さくなる可能性があります。ヒープは、重みのしきい値に達した後も同じサイズのままで、リストの最後の項目を確認した後、それをトリミングします。
ジムミッシェル、2011年

119

ただし、これはプロキシの問題には役立ちません。

1,000,000人の乗客が3000ポンドの重量を落とす場合、各乗客は(3000/1000000)を失う必要があります= 1人あたり0.003ポンド。それは、すべてのシャツや靴、あるいはおそらく爪の切り抜きさえも捨てることで達成でき、皆を救っています。これは、飛行機がより多くの燃料を使用するにつれて必要な重量損失が増加する前に、効率的な収集と投棄を前提としています。

実際、彼らはボード上で爪切りを許可しなくなったので、それは廃止されました。


14
問題を調べ、本当により良い方法を見つける能力が大好きです。
fncomp '19年

19
あなたは天才です。:)
ジョナサン

3
私は靴だけでこれをカバーすると思います
Mooing Duck

0.003ポンドは0.048オンスで、1オンスの1/20未満です。飛行機で60人に1人が3オンスのシャンプールールを利用している場合、そのシャンプーをすべて捨てるだけで1日を節約できます。
Ryan Lundy

43

以下は、簡単なソリューションのかなり単純な実装です。100%正解であるより速い方法はないと思います。

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());
        }
    }
 }

これは、しきい値に達するまで「死んだ人々」のセットを埋めることによって機能します。しきい値に達すると、最軽量の死者よりも重いものを見つけようとする乗客のリストを調べ続けます。リストが見つかったら、リストに追加して、リストから最も軽い人を「保存」し始め、それ以上保存できなくなります。

最悪の場合、これはリスト全体の一種とほぼ同じように機能します。しかし、最良の場合(「デッドリスト」は最初のX人で適切に埋められます)に実行されますO(n)


1
それ以外はtotal次の更新が必要だと思いますcontinue;、これが投稿しようとした答えです。超高速ソリューション
Mooing Duck、2011

2
これが正解です。これが最速の答えです。これは、最も複雑度の低い答えでもあります。
Xander Tulip

dead.begin()をキャッシュし、分岐を最小限に抑えるためにデータを少し再配置することで、もう少し絞ることができます。これは、最新のプロセッサでは非常に遅い
Wug

dead.begin()は、おそらく3つの要素であり、ほとんどの場合、データアクセスだけにインライン化されます。しかし、そうです、ifのいくつかを移動すると、ブランチを減らすことでパフォーマンスが少し向上します。
SoapBox 2012

1
これは論理的にエレガントで、前もって乗客の数を知らないことを含め、OPのすべての要件に対応しています。ただし、過去5か月のほとんどをSTL Maps&Setsの作業に費やしてきたため、使用したイテレータを多用するとパフォーマンスが低下することは間違いありません。セットにデータを入力し、最も重い人の合計が3,000を超えるまで右から左に繰り返します。ランダムな順序で提示された100万個の要素のセットは、i5 || i7 3.4Ghzコアで約3,000万/秒でロードされます。少なくとも100倍の低速の反復。KISSがここで優勝します。
user2548100 2013年

32

すべての乗客が協力すると仮定:並列分類ネットワークを使用します。(これも参照)

こちらがライブデモです

更新:代替ビデオ(1:00にジャンプ)

人のペアに比較交換を依頼する-これより速くなることはできません。


1
これはまだソートであり、O(nlogn)になります。O <(nlogk)where k << n、solutionが提供されているので、あなたは確かにより速くなることができます。
アダム

1
@Adam:並列ソートです。ソートには、O(nlog n)SEQUENTIALステップの下限があります。ただし、それらは並列化できるため、時間の複雑さははるかに低くなります。たとえば、cs.umd.edu /〜gasarch / ramsey / parasort.pdfを
Lior Kogan

1
まあ、OPは「これはC ++でコーディングしようとしているもののプロキシの問題です。」だから乗客が協力しても、彼らはあなたのために計算しません。すばらしいアイデアですが、nプロセッサーを手に入れるという紙の仮定は成り立ちません。
アダム

@LiorKogan-ライブデモビデオはYouTubeで利用できなくなりました
Adelin

@Adelin:ありがとう、代替ビデオが追加されました
Lior Kogan

21

@Blastfurnaceは正しい軌道に乗っていました。ピボットが重みのしきい値である場所でクイック選択を使用します。各パーティションは、1セットの人をセットに分割し、各人のセットの総重量を返します。最も体重の多い人に対応するバケットが3000ポンドを超え、そのセットに含まれる最も低いバケットが1人になるまで、適切なバケットを分割し続けます(つまり、それ以上分割することはできません)。

このアルゴリズムは線形時間償却されますが、2次ワーストケースです。これが唯一の線形時間アルゴリズムだと思います


このアルゴリズムを説明するPythonソリューションを次に示します。

#!/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)

出力:

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]

3
+1。これは興味深いアイデアですが、かなり直線的であるかどうかはわかりません。私が何かを見逃していない限り、バケットの総重量を計算するためにアイテムを反復処理する必要があり、分割するたびに高いバケットを(少なくとも部分的に)再計算する必要があります。それでも、一般的な場合のヒープベースのアプローチよりは高速ですが、複雑さを過小評価していると思います。
ジムミッシェル、2011年

2
@Jim:それは同じ複雑さでなければなりませんquickselect。私はウィキペディアの説明が最善ではないことを知っていますが、それが線形の償却期間である理由は、パーティションを作成するたびに、パーティションの片側のみで作業するためです。厳密ではありませんが、各パーティションが人々のセットを2つに分割すると想像してください。次に、最初のステップはO(n)、次にO(n / 2)などとなり、n + n / 2 + n / 4 + ... = 2nになります。
Neil G

2
@ジム:とにかく、私のアルゴリズムは最高の最悪の場合の時間を持っているのに対して、私のアルゴリズムは最高の最悪の場合の時間を持っています。どちらも良い解決策だと思います。
Neil G

2
@ JimMischel、NeilG:codepad.org/FAx6hbtc 私はすべて同じ結果であることを確認し、Jimを修正しました。FullSort:1828ティック。ジム・ミッシェル:312ティック。SoapBox 109ティック。NeilG:641ティック。
Mooing Duck

2
@NeilG:codepad.org/0KmcsvwD std :: partitionを使用して、アルゴリズムの実装を高速化しました。stdsort:1812ティック。FullHeap 312ティック。Soapbox / JimMichel:109ティック、NeilG:250ティック。
Mooing Duck、2011

11

人々の重みのように、最大​​値と最小値がO(n)でそれらをソートするために基数ソートを使用する可能性が高いことについての良い考えがあると仮定します。次に、リストの最も重いものから最も軽いものに向かって作業します。合計実行時間:O(n)。残念ながら、STLには基数ソートの実装はありませんが、書くのはかなり簡単です。


ただし、答えを導き出すためにリストを完全にソートする必要がないため、私は一般的な基数ソートを使用しません。
Mooing Duck、2011

1
明確にするために、基数ソート良い考えです。カスタマイズされた最適化されたものを必ず書いてください。
Mooing Duck、2011

1
@Mooing:完全な基数ソートを実行する必要がないことは事実ですが、私がこれを投稿した時点では、O(n)アルゴリズムは投稿されておらず、これは見やすいものでした。私はニールGの答えが彼の最も良い答えだと思います。彼はそれをより完全にそして明確に説明したので、中央値を彼の選択のピボットとして使用し始めたからです。しかし、標準の基数ソートを使用する方が少し簡単であり、微妙な実装のバグが発生する可能性は低いため、答えはそのままにしておきます。カスタマイズされた部分基数ソートを実行することは間違いなく高速ですが、漸近的にはそうではありません。
Keith Irwin

6

「ソート済み」とは異なるアボートルールで部分クイックソートを使用してみませんか。これを実行してから、上位半分だけを使用して、この上位半分内の重みに、少なくとも再帰的に1つ前のステップに戻ってリストをソートするよりも、もう捨てる必要のある重みが含まれないまで続けます。その後、そのソートされたリストの上限から人々を追い出し始めることができます。


それが、ニールGのアルゴリズムの背後にある基本的な概念だと思います
Mooing Duck、2011

それが、Neil Gが使用しているQuickselectの本質です。
マイケルドノヒュー

6

超並列トーナメントソート:-

障害の両側に標準的な3つの座席があると仮定します。

  1. 窓側の座席よりも重い場合は、窓側の座席の乗客に中央の席に移動するよう依頼してください。

  2. 中央の座席の乗客に重い場合は、通路の座​​席の乗客と交換するよう依頼してください。

  3. 左側の通路側の座席の乗客に、右側の通路側の座席の乗客と交換するよう依頼します。

  4. 右の通路側の席で乗客をバブルでソートします。(n行に対してnステップを実行します)。-右側の通路の座席にいる乗客に、前の人とn -1回交換するように依頼します。

5 3000ポンドに達するまで、ドアから蹴り出します。

3ステップ+ nステップ+本当に細い乗客の荷物がある場合は30ステップ。

2つの通路の平面の場合-指示はより複雑ですが、パフォーマンスはほぼ同じです。


Lior Koganの回答と同じですが、より詳細です。
Mooing Duck、2011

7
「十分に良い」解決策は、「無料のホットドッグ」を提供し、先頭に到達した最初の15を捨てることです。毎回最適なソリューションを提供するわけではありませんが、プレーンな「O」で実行されます。
ジェームズアンダーソン、

重いものはおそらく遅くなるので、最後の15を捨てた方がいいのではないでしょうか。
Peter

@Patriker-最小の人数で3000ポンドを失うことが目標だと思います。ただし、ステップ4を「n-29回の人と入れ替える」に変更することでアルゴリズムを最適化できますが、厳密な順序ではなく、最もポーク30が前面に表示されます。
ジェームズアンダーソン

4

おそらくstd::nth_element、最も重い20人を線形時間で分割するために使用するでしょう。次に、より複雑な方法を使用して、最も重いものを見つけてそれからバンプします。


3

リストを1回通過して平均値と標準偏差を取得し、それを使用して行かなければならない人数を概算できます。その数値に基づいてリストを生成するには、partial_sortを使用します。推測が低かった場合は、新しい推測で残りの部分にもう一度partial_sortを使用します。



2

これは、Pythonの組み込みheapqモジュールを使用したヒープベースのソリューションです。それはPythonにあるので元の質問には答えませんが、他の投稿されたPythonソリューションよりもきれい(IMHO)です。

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)

k =トスする乗客の数、N =乗客の数の場合、このアルゴリズムの最良のケースはO(N)で、このアルゴリズムの最悪のケースはNlog(N)です。最悪のケースは、kがNに長時間近い場合に発生します。これが最悪のキャストの例です。

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

ただし、この場合(パラシュートを使って人を飛行機から降ろします(私は推測します))、kは3000未満でなければなりません。これは<< "数百万人"です。したがって、平均実行時間は約Nlog(k)になるはずです。これは、人数に比例します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.