MODI method
Főnév
MODI method (tsz. MODI methods)
A MODI-módszer (teljes nevén: Modified Distribution Method, magyarul: módosított elosztási módszer) egy hatékony algoritmus a szállítási probléma optimalizálására. A célja ugyanaz, mint a Stepping-Stone módszeré: csökkenteni az összköltséget egy kezdeti, megvalósítható (feasible) szállítási terv folyamatos javításával. A MODI módszer azonban gyorsabb és strukturáltabb, mint a Stepping-Stone.
📦 A szállítási probléma röviden
Adott:
- m darab forráshely (pl. raktár), mindegyikhez tartozik egy kínálat (supply)
- n darab célállomás (pl. bolt), mindegyikhez tartozik egy igény (demand)
- Minden (i,j) cellához tartozik egy költség:
C[i][j], az egységnyi szállítás költsége
Cél:
Úgy meghatározni a szállított mennyiségeket
X[i][j], hogy teljesüljenek a kínálati és igényfeltételek, és a teljes költségΣΣ C[i][j] * X[i][j]minimális legyen.
🧩 MODI módszer alapgondolata
A MODI módszer az árnyékárakon (potenciálokon) keresztül számolja ki, hogy mely nem-alapváltozók (üres cellák) tudnának költséget csökkenteni.
A módszer a következő elven működik:
- Minden sorhoz tartozik egy Ui potenciál
- Minden oszlophoz tartozik egy Vj potenciál
- Minden báziscellára (i,j) teljesül:
Ui + Vj = C[i][j] - Ezekből a Ui és Vj értékekből kiszámíthatjuk minden üres cellára a redukált költséget:
Δ[i][j] = C[i][j] - (Ui + Vj)
Ha minden Δ[i][j] ≥ 0, akkor az aktuális megoldás optimális. Ha van olyan Δ[i][j] < 0, akkor azon a cellán keresztül lehetne javítani az összköltségen – hasonlóan, mint a Stepping-Stone módszernél.
⚙️ MODI módszer lépései
1. Kezdeti bázismegoldás elkészítése
Ehhez használhatunk például:
- North-West Corner Method
- Vogel-féle közelítés (VAM)
A megoldásnak m+n−1 darab bázisváltozót kell tartalmaznia (nem lehet kevesebb – ha mégis, degenerált, kezelni kell “ε” értékekkel).
2. Potenciálok (Ui és Vj) kiszámítása
Indítsunk valamelyik sor vagy oszlop potenciálját nulláról (Ui = 0 vagy Vj = 0).
Ezután minden báziscellára alkalmazzuk: Ui + Vj = C[i][j] és ebből kiszámítjuk a többi Ui és Vj értéket lépésenként.
3. Redukált költségek kiszámítása minden üres cellára
Minden nem-báziscellára (i,j): Δ[i][j] = C[i][j] - (Ui + Vj)
Ha minden Δ[i][j] ≥ 0 → optimális megoldás.
Ha van olyan Δ[i][j] < 0 → lehetőség az optimalizálásra.
4. Válassz egy üres cellát, ahol Δ[i][j] < 0
Ez lesz a bejövő változó. Ezen keresztül próbálunk csökkenteni a költségeken.
5. Építs ciklust a bejövő változó köré
Ugyanúgy, mint a Stepping-Stone módszernél:
- Kizárólag báziscellákon keresztül haladva
- Váltakozva vízszintesen és függőlegesen
- Kiinduló pont: az új (i,j) cella
6. + / – jelek, θ érték meghatározása
- A ciklusban váltakozó jelekkel jelöljük a cellákat:
- – + – + – …
- Azokon a helyeken, ahol mínusz van, nézzük meg a szállított mennyiségeket.
- A legkisebb ilyen érték adja θ-t (ennyi mozgatható anélkül, hogy negatívba mennénk)
7. Módosítsuk a szállított mennyiségeket a ciklus mentén
- A
+jeles cellákba hozzáadunk θ-t - A
–jeles cellákból kivonunk θ-t
8. Frissítsük a bázist
- Az új cella bekerül
- Az a báziscella, ahol 0 lett a szállított mennyiség, kiesik
9. Ismétlés
Térjünk vissza a 2. lépéshez (új potenciálok, új redukált költségek), és ismételjük, amíg nincs több Δ[i][j] < 0.
👨🏫 Egyszerű példa
Legyen az alábbi költségmátrix:
| D1 | D2 | D3 | Igény | |
|---|---|---|---|---|
| S1 | 2 | 3 | 1 | 30 |
| S2 | 5 | 4 | 8 | 50 |
| S3 | 5 | 6 | 8 | 20 |
| 20 | 40 | 40 |
- Készíts egy kezdeti megoldást (pl. VAM)
- Számítsd ki az Ui, Vj értékeket a bázis alapján
- Számítsd ki minden üres cellára
Δ[i][j] - Válassz olyan cellát, ahol Δ[i][j] < 0
- Hozz létre ciklust, módosítsd a megoldást
✅ Előnyök
- Gyorsabb, mint a Stepping-Stone, mivel nem kell minden egyes üres cellára ciklust építeni
- Könnyebben algoritmizálható
- Skálázható nagyobb mátrixokra
⚠️ Hátrányok
- A ciklusépítés továbbra is szükséges (mint Stepping-Stone-nál)
- Degenerált eseteknél bonyolult lehet
🧮 Összefoglalás
| Lépés | Művelet |
|---|---|
| 1. | Kezdeti bázismegoldás |
| 2. | Ui, Vj potenciálok kiszámítása |
| 3. | Redukált költségek (Δ) kiszámítása |
| 4. | Ha van Δ<0 → lépj tovább, különben kész vagy |
| 5. | Ciklus létrehozása a legkedvezőbb Δ[i][j] köré |
| 6. | θ meghatározása, mennyiségek módosítása |
| 7. | Bázis frissítése |
| 8. | Vissza a 2. lépéshez |
A MODI módszer tehát egy strukturált, mátrixalapú eljárás a szállítási probléma optimalizálására. Hatékony, könnyen implementálható, és gyakran része a műveletkutatás kurzusoknak, valamint gyakorlati logisztikai szoftverek belső motorjának.
📌 MODI (Modified Distribution Method) – Pszeudokód
Input:
C[m][n] // költségmátrix
S[m] // kínálatok (supply)
D[n] // igények (demand)
1. Készíts egy kezdeti bázismegoldást X[m][n], pl. VAM-mel
2. Repeat:
a. Állítsd be Ui = "?" és Vj = "?" minden i, j-hez
b. Válassz egy Ui vagy Vj értéket, pl. Ui0 := 0
c. Minden báziscellára (i, j), ahol X[i][j] > 0:
- ha Ui ismert → Vj := C[i][j] - Ui
- ha Vj ismert → Ui := C[i][j] - Vj
(ismételd, amíg lehet)
d. Minden üres (nem bázisban lévő) cellára (i,j):
Δ[i][j] := C[i][j] - (Ui + Vj)
e. Ha minden Δ[i][j] ≥ 0:
OUTPUT "Optimális megoldás", X
EXIT
f. Válaszd azt az (i*, j*) cellát, ahol Δ[i][j] a legkisebb (negatív)
g. Készíts egy zárt ciklust az (i*, j*) cella köré:
- váltakozva vízszintesen és függőlegesen
- csak báziscellákon keresztül
- térjen vissza (i*, j*)-be
h. Jelöld meg a ciklust váltakozó + és – jelekkel
(kezdj + jellel az (i*, j*) cellánál)
i. Határozd meg θ = minimum(X[i][j]) minden "–" jeles cellában
j. Módosítsd a mennyiségeket:
- "+": növeld X[i][j]-t θ-val
- "–": csökkentsd X[i][j]-t θ-val
k. Frissítsd a bázist:
- új cella bekerül
- ahol 0 lett a mennyiség, az kikerül a bázisból
Until minden Δ[i][j] ≥ 0
🔧 Kiegészítés
- A cikluskeresést (g) gráf-alapú rekurzív eljárással vagy mátrixalapú kereséssel oldjuk meg.
- Degenerált esetben (ha kevesebb mint
m+n−1báziscella van), ideiglenesen adjunk hozzá “ε” mennyiséget egy üres cellához, hogy a rendszer meghatározott legyen.
✅ C++ MODI metódus (alap implementáció)
#include <iostream>
#include <vector>
#include <limits>
#include <iomanip>
#include <queue>
using namespace std;
const int INF = 1e9;
struct Cell {
int i, j;
};
int main() {
// Bemeneti adatok
vector<vector<int>> cost = {
{19, 30, 50, 10},
{70, 30, 40, 60},
{40, 8, 70, 20}
};
vector<int> supply = {7, 9, 18};
vector<int> demand = {5, 8, 7, 14};
int m = supply.size();
int n = demand.size();
vector<vector<int>> alloc(m, vector<int>(n, -1)); // -1 jelzi az üres cellát
// North-West Corner kezdeti megoldás
vector<int> s = supply, d = demand;
int i = 0, j = 0;
while (i < m && j < n) {
int val = min(s[i], d[j]);
alloc[i][j] = val;
s[i] -= val;
d[j] -= val;
if (s[i] == 0) i++;
else j++;
}
while (true) {
// Potenciálok Ui, Vj kiszámítása
vector<int> u(m, INF), v(n, INF);
u[0] = 0;
bool updated;
do {
updated = false;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] != -1) {
if (u[i] != INF && v[j] == INF) {
v[j] = cost[i][j] - u[i];
updated = true;
} else if (v[j] != INF && u[i] == INF) {
u[i] = cost[i][j] - v[j];
updated = true;
}
}
}
}
} while (updated);
// Δ(i,j) = C(i,j) - (u[i] + v[j]) kiszámítása minden üres cellára
int minDelta = 0;
Cell enter{-1, -1};
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] == -1) {
int delta = cost[i][j] - (u[i] + v[j]);
if (delta < minDelta) {
minDelta = delta;
enter = {i, j};
}
}
}
}
// Ha nincs negatív Δ, akkor optimális megoldás
if (enter.i == -1) {
cout << "\nOptimális megoldás:\n";
int total = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] != -1) {
cout << setw(3) << alloc[i][j];
total += alloc[i][j] * cost[i][j];
} else {
cout << " -";
}
}
cout << endl;
}
cout << "Minimális költség: " << total << endl;
break;
}
// Építs ciklust (egyszerűsítve csak négyzetes ciklusra keresünk)
// Később bővíthető DFS-es általános cikluskereséssel
// Például: S3-D2-S2-D2
// Az egyszerűség kedvéért csak akkor működik jól, ha a mátrix kicsi és a ciklus egyértelműen megtalálható
// Itt most kézzel meghatározott ciklussal illusztráljuk
// Ciklus: (+enter), (-i,x), (+y,x), (-y,j)
int ei = enter.i, ej = enter.j;
int theta = INF;
Cell minus1{-1, -1}, minus2{-1, -1};
// Keresünk egy sort (i), ahol (i, ej) foglalt
for (int x = 0; x < m; ++x) {
if (alloc[x][ej] != -1 && x != ei) {
for (int y = 0; y < n; ++y) {
if (alloc[x][y] != -1 && y != ej && alloc[ei][y] != -1) {
minus1 = {x, ej};
minus2 = {ei, y};
theta = min(alloc[x][ej], alloc[ei][y]);
break;
}
}
}
}
// Ha nem találtunk ciklust → kilépés
if (theta == INF || minus1.i == -1) {
cout << "Nem sikerült ciklust építeni.\n";
break;
}
// Ciklus végrehajtása: +, -, +, -
alloc[ei][ej] = theta;
alloc[minus1.i][minus1.j] -= theta;
alloc[minus2.i][minus2.j] -= theta;
if (alloc[minus1.i][minus1.j] == 0) alloc[minus1.i][minus1.j] = -1;
if (alloc[minus2.i][minus2.j] == 0) alloc[minus2.i][minus2.j] = -1;
}
return 0;
}
✅ C++ MODI metódus (DFS)
#include <iostream>
#include <vector>
#include <limits>
#include <iomanip>
#include <tuple>
#include <stack>
#include <set>
using namespace std;
const int INF = 1e9;
struct Cell {
int i, j;
};
int m = 3, n = 4;
vector<vector<int>> cost = {
{19, 30, 50, 10},
{70, 30, 40, 60},
{40, 8, 70, 20}
};
vector<int> supply = {7, 9, 18};
vector<int> demand = {5, 8, 7, 14};
vector<vector<int>> alloc;
vector<vector<bool>> visited;
vector<Cell> path;
bool dfs(int x, int y, int targetX, int targetY, bool isRow, set<pair<int, int>>& base) {
if (x == targetX && y == targetY && !path.empty()) return true;
if (isRow) {
for (int j = 0; j < n; ++j) {
if ((x != targetX || j != targetY) && alloc[x][j] != -1 && !visited[x][j]) {
visited[x][j] = true;
path.push_back({x, j});
if (dfs(x, j, targetX, targetY, false, base)) return true;
path.pop_back();
visited[x][j] = false;
}
}
} else {
for (int i = 0; i < m; ++i) {
if ((i != targetX || y != targetY) && alloc[i][y] != -1 && !visited[i][y]) {
visited[i][y] = true;
path.push_back({i, y});
if (dfs(i, y, targetX, targetY, true, base)) return true;
path.pop_back();
visited[i][y] = false;
}
}
}
return false;
}
void printAllocation() {
cout << "Aktuális allokáció (X):\n";
int total = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] != -1) {
cout << setw(4) << alloc[i][j];
total += alloc[i][j] * cost[i][j];
} else {
cout << " -";
}
}
cout << endl;
}
cout << "Teljes költség: " << total << "\n";
}
int main() {
alloc.assign(m, vector<int>(n, -1));
vector<int> s = supply, d = demand;
// North-West Corner kezdő megoldás
int i = 0, j = 0;
while (i < m && j < n) {
int val = min(s[i], d[j]);
alloc[i][j] = val;
s[i] -= val;
d[j] -= val;
if (s[i] == 0) i++;
else j++;
}
while (true) {
vector<int> u(m, INF), v(n, INF);
u[0] = 0;
bool changed;
do {
changed = false;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] != -1) {
if (u[i] != INF && v[j] == INF) {
v[j] = cost[i][j] - u[i];
changed = true;
} else if (v[j] != INF && u[i] == INF) {
u[i] = cost[i][j] - v[j];
changed = true;
}
}
}
}
} while (changed);
int minDelta = 0;
Cell enter{-1, -1};
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (alloc[i][j] == -1) {
int delta = cost[i][j] - (u[i] + v[j]);
if (delta < minDelta) {
minDelta = delta;
enter = {i, j};
}
}
}
}
if (enter.i == -1) {
cout << "\nMODI végeredmény:\n";
printAllocation();
break;
}
// Ciklus DFS keresése
visited.assign(m, vector<bool>(n, false));
path.clear();
set<pair<int, int>> base;
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (alloc[i][j] != -1)
base.insert({i, j});
dfs(enter.i, enter.j, enter.i, enter.j, true, base);
path.insert(path.begin(), enter);
// Jelek és theta
int theta = INF;
for (int k = 1; k < path.size(); k += 2) {
theta = min(theta, alloc[path[k].i][path[k].j]);
}
for (int k = 0; k < path.size(); ++k) {
auto [x, y] = path[k];
if (alloc[x][y] == -1) alloc[x][y] = 0;
if (k % 2 == 0) alloc[x][y] += theta;
else alloc[x][y] -= theta;
if (alloc[x][y] == 0) alloc[x][y] = -1;
}
}
return 0;
}
- MODI method - Szótár.net (en-hu)
- MODI method - Sztaki (en-hu)
- MODI method - Merriam–Webster
- MODI method - Cambridge
- MODI method - WordNet
- MODI method - Яндекс (en-ru)
- MODI method - Google (en-hu)
- MODI method - Wikidata
- MODI method - Wikipédia (angol)