ハンガリー法は、割り当て問題を多項式時間で解く組み合わせ最適化 アルゴリズムであり、後の主双対法の先駆けとなった。このアルゴリズムは、1955年にハロルド・クーンによって開発され、公開された。このアルゴリズムは、ハンガリーの数学者であるデーネス・ケーニヒとイェネー・エゲルヴァーリの初期の業績に大きく基づいていたため、クーンはこのアルゴリズムに「ハンガリー法」という名前をつけた。[1] [2]しかし、2006年に、カール・グスタフ・ヤコビが19世紀に割り当て問題を解いており、その解法はヤコビの死後、1890年にラテン語で出版されていたことが発覚した。[3]
ジェームズ・マンクレス氏は1957年にこのアルゴリズムをレビューし、それが(強く)多項式であることに気づきました。[4]それ以来、このアルゴリズムは、クーン・マンクレスアルゴリズムまたはマンクレス割り当てアルゴリズムとしても知られています。元のアルゴリズムの時間計算量は でしたが、エドモンズとカープ、そして独立に富澤は、実行時間 を達成するように変更できることに気付きました。[5] [6]フォードとフルカーソンは、フォード・フルカーソンアルゴリズムの形で、この方法を一般的な最大フロー問題に拡張しました。
問題
例
この単純な例では、アリス、ボブ、キャロルという 3 人の労働者がいます。1 人はバスルームを掃除し、もう 1 人は床を掃き、3 人目は窓を拭く必要がありますが、各作業に対してそれぞれ異なる報酬を要求します。問題は、最も低コストで作業を割り当てる方法を見つけることです。この問題は、作業を行う労働者のコストのマトリックスで表すことができます。例:
ハンガリー方式を上記の表に適用すると、最小コストが算出されます。これは 15 ドルで、アリスに浴室を掃除させ、キャロルに床を掃き、ボブに窓を洗わせることで達成されます。これは、ブルート フォースを使用して確認できます。
- (担当外の人が窓を洗う)
マトリックス定式化
行列の定式化では、 n × n 行列が与えられます。ここで、 i行目とj列目の要素は、j番目のジョブをi番目の労働者に割り当てるコストを表します。各ジョブが 1 人の労働者に割り当てられ、各労働者に 1 つのジョブが割り当てられるように、割り当ての合計コストが最小になるように、労働者へのジョブの割り当てを見つける必要があります。
これは、コスト行列Cの行を並べ替えて行列の トレースを最小化することで表現できます。
ここで、P は順列行列です。 (同様に、列はCP を使用して順列化できます。)
最大コストをもたらす割り当てを見つけることが目標である場合、コスト行列C を否定することで問題を解決できます。
二部グラフの定式化
このアルゴリズムは、二部グラフを使用して問題を定式化することで同等に記述できます。n個のワーカー頂点 ( S ) とn 個のジョブ頂点 ( T )を持つ完全な二部グラフ があり、各エッジ ( E ) にはコストがあります。最小の総コストで完全なマッチングを見つけたいのです。
二部グラフに関するアルゴリズム
各 に対してとなる関数をポテンシャルと呼びます。
ポテンシャルyの値は、すべての頂点のポテンシャルの合計です。
- 。
各完全マッチングのコストは、少なくとも各ポテンシャルの値です。マッチングの合計コストは、すべてのエッジのコストの合計です。各エッジのコストは、少なくともそのエンドポイントのポテンシャルの合計です。マッチングは完全であるため、各頂点は正確に 1 つのエッジのエンドポイントです。したがって、合計コストは、少なくとも合計ポテンシャルです。
ハンガリアン法は、マッチングコストがポテンシャル値に等しいような完全マッチングとポテンシャルを見つけます。これは、両方が最適であることを証明します。実際、ハンガリアン法はタイトエッジの完全マッチングを見つけます。つまり、 の場合、エッジはポテンシャルyに対してタイトであると呼ばれます。タイトエッジのサブグラフを で表します。 における完全マッチングのコスト(存在する場合)は、の値に等しくなります。
アルゴリズムの実行中、潜在的な yと、TからSに向いたエッジがマッチングM を形成するという特性を持つの向き( で示される) を維持します。最初は、yはどこでも 0 で、すべてのエッジはSからTに向い(つまりMは空) ています。各ステップでは、 yの値を増やすように変更するか、より多くのエッジを持つマッチングを取得するように向きを変更します。M のすべてのエッジがタイトであるという不変条件を維持します。Mが完全なマッチング であれば完了です。
一般的なステップでは、およびをMでカバーされていない頂点とします(したがって、は入ってくるエッジを持たないSの頂点で構成され、出ていくエッジを持たないTの頂点で構成されます)。Zを、有向パスによってからに到達可能な頂点の集合とします。これは、幅優先探索によって計算できます。
が空でない場合は、からまでの有向パスに沿ったすべてのエッジの向きを反転します。したがって、対応するマッチングのサイズは 1 増加します。
が空の 場合は、
Δ は、マッチングがまだ最大可能サイズに達していない場合は常に、少なくとも 1 つのそのようなエッジが存在する必要があるため、明確に定義されます(次のセクションを参照)。と の間にはタイト エッジがないため、 は正です。の頂点でy をΔだけ増やし、の頂点でy をΔだけ減らします。結果のyは依然としてポテンシャルであり、グラフは変化しますが、依然としてM が含まれています (次のサブセクションを参照)。新しいエッジをSからTに向けます。 Δの定義により、から到達可能な頂点の集合Zが増加します (タイト エッジの数は必ずしも増加しないことに注意してください)。
M が完全に一致するまでこれらの手順を繰り返します。完全に一致すると、最小コストの割り当てが行われます。このバージョンの方法の実行時間は次のとおりです。Mはn回増加し、 Mが変更されないフェーズでは、最大でn 回の潜在的な変更があります( Z が毎回増加するため)。潜在的な変更に十分な時間は です。
アルゴリズムが進歩していることの証明
マッチングが最大可能サイズでない限り、アルゴリズムは常に進行できること、つまり、一致するエッジの数を増やすか、少なくとも 1 つのエッジを狭くできることを示さなければなりません。各ステップで少なくとも次のいずれかが当てはまることを示せば十分です。
- Mは最大サイズです。
- 拡張パスが含まれています。
- Gには、ルーズテールパスが含まれます。これは、 内のいずれかの頂点から内の頂点までのパスで、任意の数 (ゼロの場合もある) のタイトエッジと、それに続く 1 つのルーズエッジで構成されます。ルーズテールパスの最後のルーズエッジは からであり、 Δが明確に定義されていることを保証します。
M が最大サイズであれば、もちろんこれで終わりです。そうでない場合、Berge の補題により、基礎となるグラフGにおいてMに関する増加パスP が存在する必要があります。ただし、このパスは には存在しない可能性があります。Mの定義により、 Pのすべての偶数エッジはタイトですが、奇数エッジはルーズであるため には存在しない可能性があります。P の一方の端点は にあり、もう一方の端点は にあります。たとえば、 で始まるとします。P のすべてのエッジがタイトであれば、それは 内の増加パスのままとなり、これで終わりです。そうでない場合、 を P 上の最初のルーズなエッジとします。そうであれば、ルーズテールのパスが見つかったことになり、これで終わりです。そうでない場合、v は内の頂点からのタイトなエッジの別のパスQから到達可能です。をvから始まり最後まで続くPのサブパスとし、 を上の頂点に達するまでQに沿って移動し、次に の最後まで続くことで形成されるパスとします。は、 G内の増加パスであり、 Pよりも緩いエッジが少なくとも 1 つ少ないことに注目してください。Pは に置き換えることができ、この推論プロセスは、 G 内の増加パスまたはG内の緩いテール パスのいずれかが見つかる まで繰り返されます (正式には、緩いエッジの数に関する帰納法を使用) 。
潜在能力を調整することの証明ええ葉ま不変
M内のすべての辺がy を調整した後も残ることを示すには、 M内の任意の辺について、その両端点の両方がZにないか、どちらも Z にないかのどちらかであることを示すだけで十分です。このために、M内のTからSへの辺をとします。 M内のすべての辺はタイトであるため、 vがZにあればu もZ になければならないことは容易にわかります。ここで、矛盾するようですが、しかしu自体は、一致する辺の端点であるために存在することはできないと仮定します。そのため、 u 内の頂点からuへのタイトな辺の有向パスが存在する必要があります。このパスはv を避ける必要があります。なぜなら、仮定によりv はZに存在しないため、このパスでu の直前の頂点は別の頂点です。はTからSへのタイトな辺であるため、Mに存在します。しかし、Mには頂点u を共有する 2 つの辺が含まれるため、 Mがマッチングしているという事実と矛盾します。したがって、 M内のすべての辺は、両端点が Z にあるか、どちらの端点もZに存在しないかのどちらかです。
証明ええ潜在的
yが調整後も潜在的可能性を維持することを示すには、どのエッジもそのコストを超えて潜在的可能性の総量が増加しないことを示すだけで十分です。これは前の段落でMのエッジについてすでに確立されているので、 SからTへの任意のエッジuvを考えてみましょう。 がΔだけ増加すると、 (この場合 はΔだけ減少してエッジの潜在的可能性の総量は変化しない)または (この場合Δの定義によりが保証される)のいずれかになります。したがって、は潜在的可能性を維持します。
アルゴリズムはお(ん3) 時間
ジョブとワーカー ( )があるとします。ジョブの各プレフィックスについて、これらのジョブをそれぞれ異なるワーカーに割り当てるための最小の合計コストを計算する方法について説明します。具体的には、番目のジョブを追加し、時間 で合計コストを更新して、全体の時間計算量は になります。これは、ジョブの数がワーカーの数に比べて少ない場合 よりも優れていることに注意してください。
j番目のジョブを追加するお(jW) 時間
前のセクションと同じ表記法を使用しますが、必要に応じて定義を変更します。 は最初のジョブの集合を表し、 はすべてのワーカーの集合を表します。
アルゴリズムのステップの前に、内のすべてのジョブとポテンシャルに一致する のマッチングがあり、次の条件を満たすと仮定します。マッチングはポテンシャルに関してタイトであり、すべてのマッチングされていないワーカーのポテンシャルはゼロであり、すべてのマッチングされたワーカーのポテンシャルは非正です。このようなポテンシャルは、マッチングの最適性を証明することに注意してください。
番目のステップでは、番目のジョブを に追加してを形成し、初期化します。常に、内のすべての頂点は の 番目のジョブから到達可能です。 にはジョブが割り当てられていないワーカーが含まれていませんが、
および は、最小値が得られる任意の値を表します。前のセクションで説明した方法でポテンシャルを調整すると、 からへのタイトエッジが作成されます。
- が一致しない場合は、から へのタイトエッジのサブグラフに増加パスがあります。このパスに沿ってマッチングを切り替えると、最初のジョブが一致し、この手順は終了します。
- それ以外の場合は、とそれに一致するジョブを に追加します。
ポテンシャルの調整には時間がかかります。ポテンシャルとを変更した後で、とを再計算することも、時間内に実行できます。ケース 1 は、ケース 2 が発生して手順が終了する前に、最大で回発生する可能性があり、全体的な時間計算量は になります。
C++での実装
実装の便宜上、以下のコードでは、これまでに計算されたすべての合計の否定を格納する追加のワーカーを追加します。番目のジョブが追加され、マッチングが更新されると、現在のマッチングのコストは、これまでに計算されたすべての合計、つまり に等しくなります。
このコードはe-maxx :: algoから改変したものです。[7]
/**
* ハンガリー語を使用した https://open.kattis.com/problems/cordonbleu の解答
* アルゴリズム。
*/
#include <cassert>
#include <iostream>
#include <制限>
#include <ベクター>
名前空間std を使用します。
/**
* a = min(a, b) を設定します。
* b < a の場合は true を返す
*/
テンプレート< class T > bool ckmin ( T & a , const T & b ) { return b < a ? a = b , 1 : 0 ; }
/**
* J個のジョブとW個のワーカー(J <= W)が与えられた場合、各ジョブを割り当てるための最小コストを計算します。
* 個別のワーカーへのジョブのプレフィックス。
*
* @tparam T Jのオーダーの整数を表すのに十分な大きさの型 *
* 最大値(|C|)
* @param C 次元 JxW の行列。C[j][w] = j番目を割り当てるコスト
* ジョブを w 番目のワーカーに渡す (負の値になる可能性あり)
*
* @return 長さJのベクトル。j番目の要素は最小コストに等しい
* 最初の(j+1)個のジョブを別々のワーカーに割り当てる
*/
テンプレート< class T > vector < T > hungarian ( const vector < vector < T >> & C ) {
定数int J = ( int ) size ( C )、W = ( int ) size ( C [ 0 ] )。
アサート( J <= W );
// job[w] = w番目のワーカーに割り当てられたジョブ、またはジョブが割り当てられていない場合は-1
// 注: 便宜上、W 番目のワーカーが追加されました
ベクトル< int >ジョブ( W + 1 , -1 );
ベクトル< T > ys ( J ), yt ( W + 1 ); // ポテンシャル
// -yt[W]はすべてのデルタの合計に等しくなります
ベクトル<T>の答え;
const T inf = numeric_limits < T >:: max ();
for ( int j_cur = 0 ; j_cur < J ; ++ j_cur ) { // j_cur番目のジョブを割り当てる
w_cur はW の整数値です。
ジョブ[ w_cur ] = j_cur ;
// Z からワーカー w までのエッジの最小削減コスト
ベクトル<T> min_to ( W + 1 , inf ) ;
vector < int > prv ( W + 1 , -1 ); // 交互パス上の前のワーカー
vector < bool > in_Z ( W + 1 ); // ワーカーが Z 内にあるかどうか
while ( job [ w_cur ] != -1 ) { // 最大で j_cur + 1 回実行される
in_Z [ w_cur ] = true ;
定数int j =ジョブ[ w_cur ];
Tデルタ= inf ;
w_next ;
( int w = 0 ; w < W ; ++ w ) {
if ( ! in_Z [ w ]) {
ckmin ( min_to [ w ], C [ j ][ w ] - ys [ j ] - yt [ w ] )の場合
prv [ w ] = w_cur ;
ckmin ( delta , min_to [ w ])の場合、w_next = w ;
}
}
// デルタは常に非負になります。
// このループが最初に実行されるときを除く
// C[j_cur]のいずれかのエントリが負の場合
( int w = 0 ; w < = W ; ++ w ) {
in_Z [ w ] の場合、ys [ job [ w ]] += delta 、yt [ w ] -= delta ;
そうでない場合、min_to [ w ] -= delta ;
}
w_cur = w_next ;
}
// 交互パスに沿って割り当てを更新します
for ( int w ; w_cur != W ; w_cur = w )ジョブ[ w_cur ] =ジョブ[ w = prv [ w_cur ]];
回答.push_back ( -yt [ W ] ) ;
}
回答を返す;
}
/**
* 健全性チェック: https://en.wikipedia.org/wiki/Hungarian_algorithm#Example
* 最初の仕事(5):
* バスルームをきれいにする: ボブ -> 5
* 第一職+第二職(9):
* バスルームをきれいにする: ボブ -> 5
* フロアを掃除する: アリス -> 4
* 第一職+第二職+第三職(15):
* バスルームをきれいにする: アリス -> 8
* 床掃除: キャロル -> 4
* 窓を洗う: ボブ -> 3
*/
void sanity_check_hungarian () {
ベクトル<ベクトル< int >>コスト{{ 8 , 5 , 9 }, { 4 , 2 , 4 }, { 7 , 3 , 8 }};
assert ((ハンガリアン(コスト) ==ベクトル< int > { 5 , 9 , 15 }));
cerr << "正常性チェックに合格しました。\n " ;
}
// https://open.kattis.com/problems/cordonbleu を解決します
voidコルドンブルー() {
整数N , M ;
cin >> N >> M ;
ベクトル<ペア< int 、int >> B ( N )、C ( M );
ベクトル<ペア< int 、int >>ボトル( N )、宅配便( M );
for ( auto & b :ボトル) cin >> b . first >> b . second ;
for ( auto & c : couriers ) cin >> c . first >> c . second ;
ペア< int , int >残り;
cin >>残り.最初>>残り. 2 番目;
ベクトル<ベクトル< int >>コスト( N 、ベクトル< int > ( N + M - 1 ));
自動分散= [ & ](ペア< int 、int > x 、ペア< int 、int > y ) {
abs ( x.first - y.first ) + abs ( x.second - y.second )を返します。
};
( int b = 0 ; b < N ; ++ b )の場合{
for ( int c = 0 ; c < M ; ++ c ) { // 宅配便 -> ボトル -> レストラン
コスト[ b ][ c ] =
dist (宅配便業者[ c ],ボトル[ b ]) + dist (ボトル[ b ],残り);
}
for ( int _ = 0 ; _ < N - 1 ; ++ _ ) { // レストラン -> ボトル -> レストラン
コスト[ b ][ _ + M ] = 2 * dist (ボトル[ b ]、残り);
}
}
cout << hungarian (コスト). back () << " \n " ;
}
intメイン(){
sanity_check_ハンガリー語();
コルドンブルー();
}
連続した最短経路への接続
ハンガリアンアルゴリズムは、最小コストフローの逐次最短経路アルゴリズムと同等であると見ることができます。 [8] [9]では、ジョンソンのアルゴリズムの再重み付け手法を使用して最短経路を見つけます。前のセクションの実装は、この関係を強調するように次のように書き直されています。ワーカーのポテンシャルが、定数オフセットまで前のソリューションのポテンシャルと等しいことを確認できます。グラフがまばらな場合(ジョブとワーカーのペアのみが許可されている)、フィボナッチヒープを使用して決定することで、すべてのワーカーを反復処理して最小距離のワーカーを見つける代わりに、このアルゴリズムを時間 内に実行するように最適化できます(ここで言及されています)。
テンプレート< class T > vector < T > hungarian ( const vector < vector < T >> & C ) {
定数int J = ( int ) size ( C )、W = ( int ) size ( C [ 0 ] )。
アサート( J <= W );
// job[w] = w番目のワーカーに割り当てられたジョブ、またはジョブが割り当てられていない場合は-1
// 注: 便宜上、W 番目のワーカーが追加されました
ベクトル< int >ジョブ( W + 1 , -1 );
ベクトル< T > h ( W ); // ジョンソンポテンシャル
ベクトル<T>の答え;
T ans_cur = 0 ;
const T inf = numeric_limits < T >:: max ();
// ポテンシャル付きダイクストラ法を使用して j_cur 番目のジョブを割り当てる
( int j_cur = 0 ; j_cur < J ; ++ j_cur ) {
int w_cur = W ; // 最小距離の未訪問ワーカー
ジョブ[ w_cur ] = j_cur ;
vector < T > dist ( W + 1 , inf ); // ジョンソン縮約距離
分布[ W ] = 0 ;
vector < bool > vis ( W + 1 ); // すでに訪問したかどうか
vector < int > prv ( W + 1 , -1 ); // 最短経路上の前のワーカー
while ( job [ w_cur ] != -1 ) { // ダイクストラステップ: ヒープから最小ワーカーをポップする
T最小距離= inf ;
vis [ w_cur ] = true ;
int w_next = -1 ; // 最小距離を持つ次の未訪問ワーカー
// 最短経路を w_cur -> job[w_cur] -> w で拡張することを検討します
( int w = 0 ; w < W ; ++ w ) {
if ( ! vis [ w ]) {
// 削減されたエッジの重みの合計 w_cur -> job[w_cur] -> w
Tエッジ= C [ジョブ[ w_cur ]][ w ] - h [ w ];
w_cur ! = Wの場合{
エッジ-= C [ジョブ[ w_cur ]][ w_cur ] - h [ w_cur ];
assert ( edge >= 0 ); // ジョンソンポテンシャルの結果
}
ckmin ( dist [ w ], dist [ w_cur ] + edge )の場合、prv [ w ] = w_cur ;
ckmin ( min_dist , dist [ w ])の場合、w_next = w ;
}
}
w_cur = w_next ;
}
for ( int w = 0 ; w < W ; ++ w ) { // ポテンシャルを更新
ckmin (分布[ w ]、分布[ w_cur ] )。
h [ w ] +=距離[ w ];
}
ans_cur += h [ w_cur ];
for ( int w ; w_cur != W ; w_cur = w )ジョブ[ w_cur ] =ジョブ[ w = prv [ w_cur ]];
回答.push_back ( ans_cur ) ;
}
回答を返す;
}
マトリックス解釈
このアルゴリズムの変種は、Flood [10]によって提示された定式化に従っており、後にMunkresによってより明確に記述され、時間内で実行されることが証明されました[4]。頂点のポテンシャルを追跡する代わりに、アルゴリズムは行列に対してのみ動作します。
ここで、は元のコスト行列であり、はグラフ解釈からのポテンシャルです。ポテンシャルを変更することは、この行列の行または列を追加または削除することに対応します。アルゴリズムは から始まります。したがって、元のコスト行列を取得して変更するものと見なすことができます。
n人の労働者とタスクが与えられた場合、問題はn × nのコスト行列 の形で表される。
ここで、a、b、c、d は、タスク 1、2、3、4 を実行する必要がある労働者です。a 1、a 2、a 3、a 4 は、労働者「a」がそれぞれタスク 1、2、3、4 を実行したときに発生するペナルティを表します。
この問題は、合計ペナルティが最小になるように各作業者に固有のタスクを割り当てることと同じです。各タスクは 1 人の作業者のみが実行できることに注意してください。
ステップ1
各行について、その行のすべての要素からその最小要素が減算されます。これにより、すべての要素の値が非負になります。したがって、合計ペナルティが 0 の割り当ては、定義上、最小割り当てとなります。
これにより、各行に少なくとも 1 つのゼロが発生します。そのため、単純な貪欲アルゴリズムでは、すべてのワーカーにペナルティ 0 のタスクを割り当てようとします。これは、次の図に示されています。
上記のゼロは割り当てられたタスクになります。
最悪の場合、複数の要素が最小値である場合、複数のゼロが連続して出現する可能性があるため、試すべき組み合わせはn ! 通りあります。したがって、ある時点で、この単純なアルゴリズムは短絡される必要があります。
ステップ2
以下のマトリックスの場合のように、この段階のマトリックスは割り当てに使用できないことが判明する場合があります。
これを克服するには、すべての列に対して上記の手順を繰り返し(つまり、各列の最小要素がその列のすべての要素から減算される)、ペナルティ 0 での割り当てが可能かどうかを確認します。
ほとんどの場合、これで結果が得られますが、それでも不可能な場合は、続行する必要があります。
ステップ3
行列内のすべてのゼロは、できるだけ少ない行および/または列をマークすることによってカバーされる必要があります。手順 3 と 4 は、これを実現する1 つの方法です。
各行に任意のゼロを割り当ててみてください。割り当てられたタスクは、ゼロに星印を付けて表されます。割り当ては同じ行または列に配置できないことに注意してください。
- 行 1 の最初のゼロを割り当てます。行 1 の 2 番目のゼロは割り当てられません。
- 行 2 の最初のゼロを割り当てます。行 2 の 2 番目のゼロは割り当てられません。
- 行 3 と行 4 のゼロは、行 1 に割り当てられたゼロと同じ列にあるため、割り当てることはできません。
行と列の順序を別のものにすると、別の割り当てで終了できます。
ステップ4
(星印の付いた)ゼロを含むすべての列をカバーします。
覆われていないゼロを見つけてプライムします (プライム記号でマークします)。そのようなゼロが見つからない場合、つまりすべてのゼロが覆われている場合は、手順 5 に進みます。
- ゼロが星印のゼロと同じ行にある場合は、対応する行を覆い、星印のゼロの列を覆い隠します。
- 次に、GOTO「カバーされていないゼロを見つけて、それをプライムします。」
- ここで、行 1 の 2 番目のゼロがカバーされています。行 1 には星印が付いた別のゼロがあるため、行 1 をカバーし、列 1 をカバーします。
- 次に、行 2 の 2 番目のゼロがカバーされます。行 2 をカバーし、列 2 をカバーします。
- それ以外の場合、カバーされていないゼロには、その行に割り当てられたゼロがありません。次の手順を実行して、ゼロから始まるパスを作成します。
- サブステップ 1: 対応する列で星印の付いたゼロを探します。 ゼロがある場合はサブステップ 2 に進み、ない場合は停止します。
- サブステップ 2: 対応する行でプライムされたゼロを探します (必ず 1 つ存在します)。サブステップ 1 に進みます。
3 行目のゼロはカバーされていません。パスに 1 行目の最初のゼロを追加し、次に 1 行目の 2 番目のゼロを追加すれば完了です。
- (その他の分岐は継続) パス中に検出されたすべてのゼロについて、プライム付きのゼロには星印を付け、星印なしのゼロを付けます。
- 星印の付いたゼロを交換するときにパスがプライム付きのゼロで始まり、終わるため、ゼロをもう 1 つ割り当てました。
- (その他の分岐は続く) プライムされたすべてのゼロのプライムを解除し、すべての行を覆い隠します。
- 前の手順を繰り返します (上記の「手順 5 にスキップ」に到達するまでループを続けます)。
- 列 1、2、3 をカバーします。行 2 の 2 番目のゼロはカバーされていないため、行 2 をカバーし、列 2 をカバーします。
最小限の数の行と列ですべてのゼロがカバーされるようになりました。
前述の詳細な説明は、すべての 0 をカバーするために最小限の数の線を描く 方法の 1 つにすぎません。他の方法も同様に機能します。
ステップ5
星印の付いたゼロの数がn 個(または一般的なケースでは個、ここでnは人数、mは仕事の数)の場合、アルゴリズムは終了します。結果の解釈方法については、以下の「結果」サブセクションを参照してください。
それ以外の場合は、カバーされていない最小値を見つけます。マークされていないすべての要素からこれを減算し、2 本の線でカバーされているすべての要素に追加します。手順 4 に戻ります。
これは、カバーされていないすべての行から数値を減算し、カバーされているすべての列に同じ数値を追加することと同じです。これらの操作では、最適な割り当ては変更されません。
結果
この特定のバージョンのアルゴリズムに従う場合、星印の付いたゼロが最小の割り当てを形成します。
ケーニッヒの定理[11]によれば、最小の行数(最小頂点カバー[12] )はn(最大マッチングのサイズ[13] )となる。したがって、n 行必要な場合、最小コスト割り当ては行列のゼロのみを見ることで見つけることができる。
文献
- RE Burkard、M. Dell'Amico、S. Martello:割り当ての問題(改訂再版)。サイアム、フィラデルフィア (ペンシルバニア州) 2012。ISBN 978-1-61197-222-1
- M. Fischetti、「Lezioni di Ricerca Operativa」、Edizioni Libreria Progetto Padova、イタリア、1995年。
- R. Ahuja、T. Magnanti、J. Orlin、「ネットワークフロー」、Prentice Hall、1993 年。
- S. Martello、「Jeno Egerváry: ハンガリーのアルゴリズムの起源から衛星通信まで」。Central European Journal of Operational Research 18、47–58、2010 年
参考文献
- ^ Harold W. Kuhn、「割り当て問題に対するハンガリー法」、Naval Research Logistics Quarterly、2 : 83–97、1955年。Kuhnの初版。
- ^ Harold W. Kuhn、「割り当て問題に対するハンガリー法の変形」、Naval Research Logistics Quarterly、3 : 253–258、1956年。
- ^ “プレゼンテーション”. 2015年10月16日時点のオリジナルよりアーカイブ。
- ^ ab J. Munkres、「割り当てと輸送の問題に対するアルゴリズム」、Journal of the Society for Industrial and Applied Mathematics、5 (1):32–38、1957年3月。
- ^ エドモンズ、ジャック、カープ、リチャード M. (1972 年 4 月 1 日)。「ネットワークフロー問題におけるアルゴリズム効率の理論的改善」。Journal of the ACM。19 ( 2): 248–264。doi : 10.1145 /321694.321699。S2CID 6375478 。
- ^ 富澤 暢 (1971). 「交通ネットワーク問題の解決に役立ついくつかの技術について」.ネットワーク. 1 (2): 173–194. doi :10.1002/net.3230010206. ISSN 1097-0037.
- ^ 「割り当て問題を解決するためのハンガリーアルゴリズム」e-maxx :: algo 2012年8月23日。 2023年5月13日閲覧。
- ^ Jacob Kogler (2022年12月20日). 「最小コストフロー - 連続最短経路アルゴリズム」.競争プログラミングのためのアルゴリズム. 2023年5月14日閲覧。
- ^ 「最小コストフローを使用した割り当て問題の解決」。競技プログラミングのためのアルゴリズム。2022年7月17日。 2023年5月14日閲覧。
- ^ Flood, Merrill M. (1956). 「巡回セールスマン問題」.オペレーションズ・リサーチ. 4 (1): 61–75. doi :10.1287/opre.4.1.61. ISSN 0030-364X.
- ^ ケーニッヒの定理 (グラフ理論)ケーニッヒの定理
- ^ 頂点被覆最小頂点被覆
- ^ マッチング(グラフ理論)マッチング
外部リンク
- Bruff, Derek、「割り当て問題とハンガリー法(行列形式主義)」
- Mordecai J. Golin、「二部マッチングとハンガリー法(バイグラフ形式主義)」、コースノート、香港科技大学。
- ハンガリー最大マッチングアルゴリズム(両方の形式)、Brilliant ウェブサイト。
- RA Pilgrim、「Munkres の割り当てアルゴリズム。長方形行列用に修正」、コースノート、マレー州立大学。
- Mike Dawes、「最適割り当て問題」、コースノート、ウェスタンオンタリオ大学。
- クーンのハンガリー法について – ハンガリーからの賛辞、András Frank、Egervary Research Group、Pazmany P. setany 1/C、H1117、ブダペスト、ハンガリー。
- 講義: オペレーションズ リサーチの基礎 - 割り当て問題 - ハンガリー アルゴリズム、G. スリニヴァサン教授、インド工科大学マドラス校、経営学部。
- 拡張: 割り当て感度分析 (時間計算量 O(n^4))、Liu、Shell。
- あらゆる課題問題をオンラインで解決し、ハンガリーアルゴリズムのステップバイステップの説明を提供します。
実装
時間の計算量を満たしていると主張していても、これらすべてが満たされているわけではないことに注意してください。一部にはエラーが含まれていたり、より遅いアルゴリズムを実装していたり、その他の非効率性があったりする場合があります。最悪の場合、Wikipedia からリンクされたコード例が後で変更されてエクスプロイト コードが含まれる可能性があります。不明な作成者によるこのようなコード例を使用する場合は、検証とベンチマークが必要です。
- RA Pilgrim のコードの Lua 版と Python 版 (時間計算量は O ( n 3 ) {\displaystyle O(n^{3})} と主張)
- Juliaの実装
- C実装ではO ( n 3 ) {\displaystyle O(n^{3})}の時間計算量を要求する
- Java実装ではO ( n 3 ) {\displaystyle O(n^{3})}の時間計算量であると主張している
- Python実装
- ユニットテスト付きのRuby実装
- C#実装では O ( n 3 ) {\displaystyle O(n^{3})} の時間計算量であると主張している
- ユニットテスト付きのD実装(O ( n 3 ) {\displaystyle O(n^{3})} と主張するJavaバージョンの移植)2019年12月30日にWayback Machineにアーカイブされました
- オンラインインタラクティブ実装
- シリアル実装とパラレル実装。
- Matlab と C 2008 年 5 月 3 日にWayback Machineにアーカイブされました
- Perl実装
- C++実装
- O ( n 3 ) {\displaystyle O(n^{3})} の時間計算量を要求する C++ 実装 (BSD スタイルのオープンソースライセンス)
- MATLAB実装
- C実装
- ユニットテスト付きのJavaScript実装(O ( n 3 ) {\displaystyle O(n^{3})}の時間計算量を主張するJavaバージョンの移植)
- Clue Rパッケージは、solve_LSAPという実装を提案している。
- GitHub 上の Node.js 実装
- scipy パッケージの Python 実装
