Агуулгыг алгасах

Д´Эсопо-Папегийн алгоритм

$w_i$ жинтэй $m$ ирмэг, $n$ оройтой граф ба эхлэх орой $v_0$ өгөгдсөн. Даалгавар бол $v_0$ оройноос бусад орой бүр хүртэлх хамгийн богино замыг олох явдал юм.

Д´Эсопо-Папегийн алгоритм ихэнх тохиолдолд Дейкстрагийн алгоритм ба Беллман-Фордын алгоритмаас хурдан ажиллах ба сөрөг ирмэгийн хувьд ч ажиллана. Гэвч сөрөг циклийн хувьд ажиллахгүй.

Тайлбар

$d$ массив хамгийн богино замын уртуудыг агуулна гэе, өөрөөр хэлбэл $d_i$ нь $v_0$ оройноос $i$ орой хүртэлх хамгийн богино замын одоогийн урт юм. Эхэндээ энэ массив $d_{v_0} = 0$-ээс бусад орой бүрийн хувьд төгсгөлгүйгээр дүүрсэн байна. Алгоритм дууссаны дараа энэ массив хамгийн богино зайнуудыг агуулна.

$p$ массив одоогийн өвгүүдийг агуулна гэе, өөрөөр хэлбэл $p_i$ нь $v_0$-ээс $i$ хүртэлх одоогийн хамгийн богино зам дээрх $i$ оройн шууд өвөг юм. $d$ массивын нэгэн адил $p$ массив алгоритмын явцад аажмаар өөрчлөгдөж, эцэст нь эцсийн утгуудаа авна.

Одоо алгоритм руугаа орьё. Алхам бүрд оройнуудын гурван олонлогийг хөтөлнө:

  • $M_0$ — зай нь аль хэдийн тооцоологдсон оройнууд (хэдийгээр эцсийн зай биш байж болно)
  • $M_1$ — зай нь одоогоор тооцоологдож байгаа оройнууд
  • $M_2$ — зай нь хараахан тооцоологдоогүй оройнууд

$M_1$ олонлог дахь оройнуудыг хоёр талтай дараалалд (deque) хадгална.

Алгоритмын алхам бүрд бид $M_1$ олонлогоос (дарааллын урд талаас) орой авна. $u$ нь сонгосон орой байг. Бид энэ $u$ оройг $M_0$ олонлогт оруулна. Дараа нь бид энэ оройноос гарах бүх ирмэгийг тойрно. $v$ нь одоогийн ирмэгийн хоёр дахь үзүүр, $w$ нь түүний жин байг.

  • Хэрэв $v$ нь $M_2$-т харьяалагдвал $v$-г дарааллын хойд талд оруулах замаар $M_1$ олонлогт оруулна. $d_v$$d_u + w$ гэж тохируулна.
  • Хэрэв $v$ нь $M_1$-д харьяалагдвал бид $d_v$ утгыг сайжруулахыг оролдоно: $d_v = \min(d_v, d_u + w)$. $v$ аль хэдийн $M_1$-д байгаа тул бид түүнийг $M_1$ ба дараалалд оруулах шаардлагагүй.
  • Хэрэв $v$ нь $M_0$-д харьяалагдах бөгөөд $d_v$-г сайжруулж болох ($d_v > d_u + w$) бол бид $d_v$-г сайжруулж, $v$ оройг $M_1$ олонлогт буцаан оруулж, дарааллын эхэнд байрлуулна.

Мэдээж $d$ массив дахь шинэчлэл бүрийн хамт бид $p$ массив дахь харгалзах элементийг ч шинэчлэх ёстой.

Implementation

We will use an array $m$ to store in which set each vertex is currently.

struct Edge {
    int to, w;
};

int n;
vector<vector<Edge>> adj;

const int INF = 1e9;

void shortest_paths(int v0, vector<int>& d, vector<int>& p) {
    d.assign(n, INF);
    d[v0] = 0;
    vector<int> m(n, 2);
    deque<int> q;
    q.push_back(v0);
    p.assign(n, -1);

    while (!q.empty()) {
        int u = q.front();
        q.pop_front();
        m[u] = 0;
        for (Edge e : adj[u]) {
            if (d[e.to] > d[u] + e.w) {
                d[e.to] = d[u] + e.w;
                p[e.to] = u;
                if (m[e.to] == 2) {
                    m[e.to] = 1;
                    q.push_back(e.to);
                } else if (m[e.to] == 0) {
                    m[e.to] = 1;
                    q.push_front(e.to);
                }
            }
        }
    }
}

Complexity

Алгоритм ихэвчлэн нэлээд хурдан ажилладаг — ихэнх тохиолдолд Дейкстрагийн алгоритмаас ч хурдан. Гэвч алгоритм экспоненциал хугацаа авдаг тохиолдол байдаг ба энэ нь түүнийг хамгийн муу тохиолдолд тохиромжгүй болгодог. Лавлагаа болгон Stack Overflow ба Codeforces дээрх хэлэлцүүлгийг үзнэ үү.