Zaawansowane algorytmy i struktury danych

Opis

Kontynuacja przedmiotu ,,Algorytmy i struktury danych''. Zawartość w przeważającej części dotyczy zagadnień optymalizacji kombinatorycznej w ujęciu algorytmicznym (problemy najkrótszych ścieżek, skojarzenia, problemy przepływowe, programowanie liniowe, algorytmy aproksymacyjne). Ponadto, przedstawiono wybrane zagadnienia algorytmów randomizowanych, geometrii obliczeniowej, algorytmów równoległych oraz szybkiej arytmetyki wielomianów.

Autorzy

Krzysztof Diks, Łukasz Kowalik, Wojciech Rytter, Piotr Sankowski — Uniwersytet Warszawski, Wydział Matematyki, Informatyki i Mechaniki

Wymagania wstępne

Zawartość

Dodatkowe zagadnienia

Literatura

Przepływ maksymalny o minimalnym koszcie

Definicja problemu

W tym wykładzie wymagamy od Czytelnika znajomości wykładu o maksymalnym przepływie.

Egzemplarz problemu maksymalnego przepływu o minimalnym koszcie stanowi graf skierowany \(G=(V,E)\), funkcje \(c:E\rightarrow\mathbb{R}_{\ge 0}\) i \(a:E\rightarrow \mathbb{R}\}\) oraz dwa wyróżnione wierzchołki \(s\) i \(t\). Podobnie jak w wykładzie o maksymalnym przepływie, \(c\) nazywamy funkcją przepustowości. Natomiast \(a\) jest funkcją kosztu oraz \(a(e)\) nazywamy kosztem krawędzi \(e\). W grafie \(G\) dopuszczamy możliwość istnienia wielu krawędzi o tym samym początku i końcu. Jest to naturalne założenie, gdyż koszty tych krawędzi mogą się różnić.

Przepływem nazywamy dowolną funkcję \(f:E\rightarrow\mathbb{R}_{\ge 0}\) taką, że:

Zauważmy, że powyższa definicja różni się nieco od definicji przepływu z wykładu o maksymalnym przepływie. Różnica polega na tym, że w tym rozdziale przepływ nie spełnia warunku skośnej symetryczności. Konieczność użycia nowej definicji wynika z faktu istnienia wielu krawędzi o tym samym początku i końcu. Oczywiście obie definicje są równoważne dla grafów, w których dla dowolnych wierzchołków \(u\) i \(v\) jest co najwyżej jedna krawędź spośród krawędzi \((u,v)\), \((v,u)\). Używana w tym rozdziale definicja jest bardziej naturalna i wygodniejsza podczas implementacji algorytmów, choć mniej wygodna przy dowodzeniu.

Kosztem przepływu \(f\) nazywamy liczbę \(cost(f) = \sum_{e\in E} a(e) f(e)\). Zgodnie z tą definicją, wartość \(a(e)\) interpretujemy jako koszt przesłania 1 jednostki przepływu po krawędzi \(e\). W tym rozdziale zajmujemy się następującym problemem optymalizacyjnym.

Problem 1
Znaleźć w sieci \((G,a,c,s,t)\) przepływ maksymalny o minimalnym koszcie (wśród przepływów maksymalnych).

Oprócz problemu 1, nieco inne zagadnienie wydaje się równie naturalne:

Problem 2
Dla danej sieci \((G,a,c,s,t)\) i liczby \(w \in \mathbb{R_{\ge 0}}\) znaleźć przepływ o minimalnym koszcie wśród przepływów o wartości \(w\).

Nietrudno dostrzec, że problemy 1 i 2 są równoważne. Oczywiście skoro umiemy wyznaczyć wartość maksymalnego przepływu, to problem 1 sprowadza się do problemu 2. Z drugiej strony, jeśli umiemy rozwiązać problem 1, mając dany egzemplarz \((G,a,c,s,t,w)\) problemu 2 wystarczy rozwiązać problem 1 dla egzemplarza \((G',a,c,s',t)\), gdzie graf \(G'\) powstaje z \(G\) przez dodanie nowego wierzchołka \(s'\) oraz krawędzi \((s',s)\) o koszcie 0 i przepustowości \(w\). W dalszej części wykładu będziemy mówić już wyłącznie o problemie 1.

Sieć residualna

Wprowadzenie kosztów krawędzi do naszego problemu wymusza nieco inną definicję sieci residualnej, jednak jej rola pozostaje ta sama. Dla danego przepływu \(f\) w sieci \((G=(V,E),a,c,s,t)\), sieć residualna \(G_f\) ma ten sam zbiór wierzchołków \(V\) co sieć \(G\). Krawędzie \(G_f\) chcemy określić w taki sposób, że przesłanie jednostki przepływu w \(G_f\) po krawędzi \(e=(u,v)\) odpowiada powiększeniu \(f\) wzdłuż krawędzi \(e\) o tę jednostkę lub pomniejszeniu \(f\) o tę jednostkę wzdłuż odwrotnej krawędzi, czyli wzdłuż \((v,u)\).

Zgodnie z powyższą intuicją, dla każdej krawędzi \(e\in E\) takiej, że \(f(e) < c(e)\), w zbiorze \(E_f\) sieci \(G_f\) umieszczamy krawędź \(e\) o przepustowości residualnej \(c_f(e) = c(e)-f(e)\) i koszcie \(a_f(e)=a(e)\). Natomiast dla każdej krawędzi \(e=(u,v)\in E\) takiej, że \(f(e) > 0\), w \(E_f\) umieszczamy krawędź \(\text{rev}(e)=(v,u)\) o przepustowości residualnej \(c_f(\text{rev}(e)) = f(e)\) i koszcie \(a_f(\text{rev}(e))=-a(e)\).

Jeśli \(f\) jest przepływem w \(G\), natomiast \(g\) jest przepływem w \(G_f\), to \(f+g\) definiujemy jako przepływ w \(G\), gdzie dla \(e\in E\) określamy \((f+g)(e) = f(e) + g(e) - g(\text{rev}(e))\). Łatwo widać, że \(|f+g| = |f|+|g|\) oraz \(cost(f+g)=cost(f)+cost(g)\).

Podobnie, jeśli \(f\) i \(f'\) są przepływami w \(G\) to \(f'-f\) definiujemy jako następującą funkcję \((f'-f):E_f \rightarrow\mathbb{R}_{\ge 0}\). Dla \(e \in E\), jeśli \(f'(e) > f(e)\), to \((f'-f)(e)=f'(e)-f(e)\), natomiast jeśli \(f'(e) < f(e)\), to \((f'-f)(\text{rev}(e))=f(e)-f'(e)\). Na pozostałych elementach \(E_f\) funkcja \(f'-f\) ma wartość 0. Łatwy dowód poniższego lematu pozostawiamy Czytelnikowi.

Lemat 3
Funkcja \((f'-f)\) jest przepływem w sieci residualnej \(G_f\) o wartości \(|f'-f|=|f'|-|f|\) i koszcie \(cost(f'-f)=cost(f')-cost(f)\). ♦

Algorytm usuwania ujemnych cykli

Następujący lemat jest często wykorzystywany w problemach związanych z przepływami.

Lemat 4
Każdy przepływ można rozłożyć na sumę cykli i ścieżek prostych od \(s\) do \(t\).

Dowód
Niech \(f\) będzie dowolnym przepływem. Użyjemy indukcji po liczbie krawędzi \(e\) takich, że \(f(e) > 0\).

Baza indukcji. Jeśli nie ma takich krawędzi to teza lematu jest prawdziwa (suma pusta).

Krok indukcyjny. Jeśli istnieje cykl \(C\), którego krawędzie mają dodatni przepływ, to definiujemy przepływ \(g\), który jest zerowy wszędzie poza \(C\), a na wszystkich krawędziach \(C\) ma wartość \(min_{e\in C} f(e)\). Żądaną w tezie rodzinę cykli i ścieżek otrzymujemy rozkładając na podstawie założenia indukcyjnego przepływ \(f-g\) na cykle i ścieżki i dodając do tej rodziny cykl \(C\).

Załóżmy więc, że nie istnieje opisany wyżej cykl. Niech \(e_0=(v_0,v_1)\) będzie dowolną krawędzią taką, że \(f(e_0)>0\). Wówczas z warunku zachowania przepływu albo \(v_1=t\) albo istnieje krawędź \(e_1=(v_1, v_2)\) taka, że \(f(e_1)>0\). Ponownie, albo \(v_2=t\) albo istnieje krawędź \(e_2=(v_2,v_3)\)... itd. Ponieważ wykluczyliśmy cykle, ta marszruta będzie ścieżką prostą, a więc ponieważ graf jest skończony, musi zakończyć się w \(t\). Podobnie, z warunku zachowania przepływu albo \(v_0=s\) albo istnieje krawędź \(e_{-1}=(v_{-1}, v_0)\) taka, że \(f(e_{-1})>0\). Ponownie, albo \(v_{-1}=s\) albo istnieje krawędź \(e_{-2}=(v_{-2},v_{-1})\)... itd: otrzymujemy ścieżkę prostą od \(s\) do \(v_0\). Suma tych dwóch ścieżek musi być ścieżką prostą od \(s\) do \(t\). Korzystając z założenia indukcyjnego analogicznie jak w przypadku cyklu, dostajemy tezę lematu. ♦

Jeśli \(X\) jest zbiorem krawędzi, to kosztem \(X\) nazwiemy \(a(X)=\sum_{e\in X}a(e)\). Przez koszt ścieżki lub cyklu rozumiemy koszt odpowiedniego zbioru krawędzi.

Lemat 5
Niech \(f\) będzie przepływem w \((G,a,c,s,t)\). Wówczas \(f\) jest przepływem o minimalnym koszcie wśród przepływów o wartości \(|f|\) wtw. gdy \(G_f\) nie zawiera cykli o ujemnym koszcie.

Dowód
Implikacja \((\rightarrow)\) jest oczywista, gdyż dodanie do \(f\) cyklu o ujemnym koszcie nie zmienia wartości \(f\), natomiast zmniejsza jego koszt.

Zajmijmy się implikacją \((\leftarrow)\). Niech \(f'\) będzie dowolnym przepływem o wartości \(|f'|=|f|\). Z lematu 3 \(f'-f\) jest przepływem w \(G_f\) o wartości 0. Z lematu 4 \(f'-f\) rozkłada się na sumę cykli i ścieżek od \(s\) do \(t\). Ponieważ \(|f'-f|=0\), więc w tym rozkładzie nie ma żadnych ścieżek. Z założenia, każdy z pozostałych cykli ma nieujemny koszt. Stąd, \(cost(f'-f) \ge 0\), a więc z lematu 3, \(cost(f')\ge cost(f)\). ♦

Korzystając z powyższego lematu dostajemy prosty algorytm znajdowania maksymalnego przepływu o minimalnym koszcie:

  1. Znajdź maksymalny przepływ \(f\) w \((G,c,s,t)\), np. algorytmem Forda-Fulkersona (patrz odpowiedni wykład),
  2. Tak długo, jak \(G_f\) zawiera cykl \(C\) o ujemnym koszcie (taki cykl wykrywamy za pomocą algorytmu Bellmana-Forda -- patrz odpowiedni wykład), znajdź taki cykl, i zbuduj przepływ \(g\) w \(G_f\), który dla każdej krawędzi \(e \in E(C)\) ma \(g(e)=\min_{e\in E(C)}c_f(e)\), a poza \(E(C)\) jest zerowy, a następnie wykonaj \(f := f + g\).

Jako ćwiczenie pozostawiamy pokazanie, że algorytm Bellmana-Forda można rozszerzyć tak, aby nie tylko wykrywać istnienie ujemnych cykli, ale także znaleźć pewien taki cykl w czasie \(O(|V|\cdot |E|)\).

Podobnie jak algorytm Forda-Fulkersona, powyższy algorytm nie ma własności stopu gdy przepustowości są liczbami rzeczywistymi. Jeśli jednak przepustowości i koszty są wymierne, algorytm zatrzyma się, zwracając poprawną odpowiedź. Czas jego działania będzie pseudowielomianowy (tzn. zależy wielomianowo od \(|V|\) oraz \(\max_{e\in E} c(e)\) i \(\max_{e\in E} a(e)\), zakładając że \(c\) i \(a\) są całkowitoliczbowe). W kolejnym rozdziale przedstawimy bardziej efektywny algorytm, działający w szczególnym przypadku, gdy dana na wejściu sieć nie ma cykli o ujemnym koszcie.

Algorytm najtańszej ścieżki

Załóżmy, że w sieci \((G,a,c,s,t)\) danej na wejściu nie ma cykli o ujemnym koszcie. Wówczas możemy zastosować następujący naturalny algorytm.

  1. Niech \(f\) będzie przepływem zerowym.
  2. Tak długo, jak \(G_f\) zawiera ścieżkę powiększającą, powiększ \(f\) wzdłuż najtańszej ścieżki powiększającej \(P\): przesyłamy \(\min_{e\in E(P)} c_f(e)\) jednostek przepływu.

Poprawność algorytmu

Zacznijmy od udowodnienia poprawności powyższego algorytmu, zwanego algorytmem najtańszej ścieżki. Z twierdzenia o maksymalnym przepływie i minimalnym przekroju, otrzymujemy że uzyskany po zakończeniu algorytmu przepływ \(f\) faktycznie jest maksymalny (zauważmy, ze nasz algorytm jest po prostu pewną implementacją algorytmu Forda-Fulkersona). Pozostaje pokazać, że \(f\) ma minimalny koszt. Wynika to z następującego niezmiennika pętli w naszym algorytmie:

Niezmiennik 1: \(f\) ma minimalny koszt wśród przepływów o wartości \(|f|\).

Zauważmy, że niezmiennik 1 jest spełniony na początku algorytmu, dla przepływu zerowego, bowiem dowolny przepływ o wartości 0 z lematu 4 rozkłada się na sumę cykli, a wiemy że cykle te mają nieujemne koszty: stąd, dowolny przepływ zerowy ma nieujemny koszt. Udowodnimy teraz, że niezmiennik 1 pozostaje zachowany po obrocie pętli.

Lemat 6
Niech \(f\) będzie pewnym przepływem o minimalnym koszcie spośród przepływów o wartości \(|f|\). Niech \(g\) będzie przepływem w sieci residualnej \(G_f\), który jest niezerowy tylko na krawędziach pewnej najtańszej ścieżki \(P\) od \(s\) do \(t\). Wówczas \(f+g\) jest najtańszym przepływem o wartości \(|f+g|\).

Dowód
Niech \(f'\) będzie dowolnym przepływem o wartości \(|f+g|\). Pokażemy, że \(cost(f')\ge cost(f+g)\).

Z lematu 3, \(f'-f\) jest przepływem w \(G_f\), o wartości \(|f'-f|=|f'|-|f|=|f+g|-|f|=|g|\). Z lematu 4, \(f'-f\) można rozbić na sumę cykli \(C_1,\ldots,C_k\) i ścieżek \(P_1\ldots,P_m\) od \(s\) do \(t\). Ponieważ \(f\) ma minimalny koszt, więc na mocy lematu 5 w \(G_f\) nie ma cykli o ujemnym koszcie, więc każdy z cykli \(C_1,\ldots,C_k\) ma nieujemny koszt. Z kolei dla każdego \(i=1,\ldots,m\) mamy \(a(P_i) \ge a(P)\), gdyż \(P\) jest najtańszą ścieżką. Oznaczmy przez \(|C_i|\) wartość przepływu, który płynie po cyklu \(C_i\), analogicznie definiujemy \(|P_i|\). Razem dostajemy

\[cost(f'-f) = \sum_{i=1}^k |C_i| a(C_i) + \sum_{i=1}^m |P_i| a(P_i) \ge \sum_{i=1}^m |P_i| a(P_i) \ge a(P) \sum_{i=1}^m |P_i| \ge a(P) |f'-f| = a(P) |g| = cost(g).\]

Ponieważ z lematu 3, \(cost(f'-f) = cost(f') - cost(f)\), otrzymujemy \(cost(f') \ge cost(f)+cost(g) = cost(f+g)\). ♦

Złożoność algorytmu

Złożoność algorytmu zależy od tego, w jaki sposób wyszukujemy najtańszą ścieżkę w grafie. Zauważmy, że w sieci residualnej pojawiają się krawędzie o ujemnym koszcie, nie możemy więc użyć algorytmu Dijkstry. Jeśli skorzystamy z algorytmu Bellmana-Forda otrzymamy złożoność \(O(|f|\cdot|V|\cdot|E|)\).

Aby poprawić złożoność algorytmu skorzystamy z tricku podobnego jak w algorytmie Johnsona ((patrz odpowiedni wykład)). Przypomnijmy, że jeśli mamy funkcję \(\pi:V\rightarrow\mathbb{R}\) oraz dla każdej krawędzi \(e=(u,v)\) określimy \(a_{\pi}(e) = a(e) + \pi(u) - \pi(v)\) to wówczas najtańsza ścieżka względem wagi \(a_{\pi}\) jest także najtańszą ścieżką względem wagi \(a\) (lemat 8 z powyższego wykładu). Co więcej, jeśli \(\pi\) jest potencjałem, to \(a_{\pi}(e)\ge 0\) dla każdej krawędzi \(e\). Pomysł polega na tym, żeby najtańsze ścieżki obliczać algorytmem Dijkstry w sieci o wagach \(a_{\pi}\). Początkowy potencjał \(\pi\) wyznaczamy za pomocą algorytmu Bellmana-Forda, podobnie jak to było w algorytmie Johnsona. Okazuje się, że po zakończonej iteracji, potencjał dla nowej sieci residualnej można łatwo obliczyć w czasie liniowym.

Zmodyfikowany algorytm wygląda następująco.

  1. Niech \(f\) będzie przepływem zerowym.
  2. Za pomocą algorytmu Bellmana-Forda, dla każdego wierzchołka oblicz \(\pi(v)\), koszt najtańszej ścieżki od \(s\) do \(v\).
  3. Tak długo, jak \(G_f\) zawiera ścieżkę powiększającą, wykonuj:
    • Uruchom algorytm Dijkstry z wierzchołka \(s\) w grafie \(G_f\) z kosztami krawędzi \(a_{\pi}\). Algorytm ten znajdzie:
      • najtańszą ścieżkę powiększającą \(P\) względem kosztów \(a_{\pi}\), oraz
      • dla każdego wierzchołka \(v \in V\) obliczy \(\delta_{\pi}(v)\), koszt najtańszej ścieżki od \(s\) do \(v\).
    • Powiększ \(f\) wzdłuż \(P\) przesyłając \(\min_{e\in E(P)} c_f(e)\) jednostek przepływu.
    • Dla każdego wierzchołka \(v \in V\) przypisz \(\pi(v) := \pi(v) + \delta_{\pi}(v)\).

Poprawność algorytmu wynika z następującego niezmiennika.

Niezmiennik 2: \(\pi\) jest potencjałem w grafie \(G_f\).

Na początku, gdy \(\pi\) jest po prostu funkcją odległości \(\delta\) w grafie, w którym za długość krawędzi przyjmujemy jej koszt, niezmiennik jest oczywisty, gdyż dla każdej krawędzi \(u,v\), mamy \(\delta(v) \le \delta(u) + a(u,v)\).

Dowód, że niezmiennik jest zachowany po obrocie pętli
Niech \(f\) będzie przepływem przed iteracją, \(f'\) przepływem po iteracji. Zakładamy, że \(\pi\) jest potencjałem w \(G_f\) tzn. dla każdej krawędzi \(e=(u,v)\in E_f\) mamy \(a_{\pi}(e)\ge 0\). Pokażemy, że \(\pi'=\pi+\delta\) jest potencjałem w \(G_{f\,^\prime}\), tzn. że dla każdej krawędzi \(e=(u,v)\in E_{f\,'}\) mamy \(a_{\pi'}(e)\ge 0\). Rozważmy dowolną krawędź \(e=(u,v)\in E_{f\,'}\).

Jeśli \(e \in E_f\), to \(\delta_{\pi}(v) \le \delta_{\pi}(u) + a_{\pi}(e)\). Stąd, \(0 \le a_{\pi}(e) + \delta_{\pi}(u) - \delta_{\pi}(v) = a(e) + \pi(u) - \pi(v) + \delta_{\pi}(u) - \delta_{\pi}(v) = a(e) + \pi'(u) - \pi'(v) = a_{\pi'}(e)\).

Jeśli natomiast \(e \not \in E_f\), to wiemy, że podczas iteracji przesyłano przepływ po krawędzi \(\text{rev}(e)\in E_f\). Stąd, krawędź \(\text{rev}(e)=(v,u)\) leży na najtańszej ścieżce od \(s\) do \(t\) w \(G_f\) z kosztami \(a_{\pi}\), a więc \(\delta_{\pi}(u)=\delta_{\pi}(v)+a_{\pi}(\text{rev}(e)) = \delta_{\pi}(v) + a(\text{rev}(e)) + \pi(v) - \pi(u)\). Stąd, \(\pi'(v) - \pi'(u) + a(\text{rev}(e)) = 0\). Ponieważ \( a(\text{rev}(e))=- a(e)\) otrzymujemy \(\pi'(u) - \pi'(v) + a(e) = 0\), czyli \(a_{\pi'}(e)=0\). ♦

Jeśli algorytm Dijkstry zaimplementujemy z użyciem kopców Fibonacciego to jedna iteracja naszego algorytmu zajmie czas \(O(|V|\log|V|+|E|)\). Otrzymujemy zatem następujące twierdzenie.

Twierdzenie
W sieci bez cykli o ujemnym koszcie można znaleźć najtańszy maksymalny przepływ \(f\) w czasie \(O(|V|\cdot|E|+|f|(|V|\log|V|+|E|))\).

Najcięższe / najlżejsze skojarzenia w grafach dwudzielnych

Niech \(G=(V,E)\) będzie grafem skierowanym oraz niech \(w:E \rightarrow \mathbb{R}\) będzie dowolną funkcją wag krawędzi. Wagą skojarzenia \(M\) nazywamy liczbę \(w(M) = \sum_{e\in M} w(e)\). W problemie najlżejszego (najcięższego) skojarzenia należy wyznaczyć skojarzenie doskonałe (tzn. takie, że każdy wierzchołek jest skojarzony) o minimalnej (maksymalnej) wadze. Oczywiście problem najcięższego skojarzenia redukuje się do problemu najlżejszego skojarzenia: wystarczy przemnożyć wagi krawędzi przez \(-1\).

Łatwo zauważyć, że problem najlżejszego skojarzenia w grafie dwudzielnym \(G=(U,V,E)\) redukuje się do problemu maksymalnego przepływu o minimalnym koszcie. Mianowicie, budujemy sieć o zbiorze wierzchołków \(U \cup V \cup \{s,t\}\). Dla każdego wierzchołka \(u\in U\), umieszczamy w sieci krawędź \((s,u)\) o przepustowości 1 i koszcie 0. Podobnie, dla każdego wierzchołka \(v\in V\), umieszczamy w sieci krawędź \((v,t)\) o przepustowości 1 i koszcie 0. W końcu, dla każdej krawędzi \(uv \in E\) takiej że \(u\in U\) oraz \(v\in V\), umieszczamy w sieci krawędź \((u, v)\) o przepustowości 1 i koszcie \(w(u,v)\). Łatwo sprawdzić, że w takiej sieci nie ma cykli o ujemnym koszcie oraz że dowolny przepływ \(f\) o wartości \(|f|=|U|=|V|\) odpowiada skojarzeniu (jakiemu?) o wadze \(cost(f)\).

Wniosek
Najlżejsze (najcięższe) skojarzenie doskonałe w grafie dwudzielnym o \(n\) wierzchołkach i \(m\) krawędziach można wyznaczyć w czasie \(O(nm+n^2\log n)\).

Okazuje się, że nawet w dowolnym grafie możliwe jest wyznaczenie najlżejszego skojarzenia doskonałego w czasie wielomianowym. Algorytm ten nie korzysta ze sprowadzenia do przepływów i wykracza poza zakres tego wykładu.

Programowanie liniowe 1: Podstawowe pojęcia i geometria programów liniowych

Wstęp i podstawowe pojęcia

 

Co to jest programowanie liniowe?

Program liniowy (w skrócie PL) jest to problem minimalizacji/maksymalizacji liniowej funkcji celu o \(n\) argumentach \(x_1, x_2, \ldots, x_n\) przy zachowaniu pewnej liczby równości lub nierówności liniowych (będziemy je nazywać ograniczeniami) zawierających zmienne \(x_i\).

Przykład 1: prosty program liniowy

\(
\begin{array}{lr}
\text{zminimalizuj} & x_1 + 2x_2 \\
\text{z zachowaniem warunków} & x_2 \le x_1 + 2 \\
& 2x_1+x_2\le 4\\
& 2x_2+x_1\ge 0\\
& x_2 \ge 0
\end{array}
\)

Za pomocą programów liniowych można wyrazić bardzo wiele naturalnych problemów optymalizacyjnych. Dla przykładu, problem maksymalnego przepływu w sieci o \(n\) wierzchołkach, źródle \(s\), ujściu \(t\), i funkcji przepustowości \(c:V^2\rightarrow \mathbb{R}\) można wyrazić za pomocą następującego programu liniowego o \(|V|^2\) zmiennych (dla każdej pary wierzchołków \(v,w\) mamy zmienne \(f_{vw}\) i \(f_{wv}\) odpowiadające przepływowi od \(v\) do \(w\) i odwrotnie) i \(2|V|^2+|V|-2\) ograniczeniach:

Przykład 2: program liniowy dla problemu maksymalnego przepływu
\[
\begin{array}{ll@{\hspace{25mm}}l}
\textrm{zmaksymalizuj} & \sum_{v\in V} f_{sv} & \\
\textrm{z zachowaniem warunków} & f_{vw} \le c(v,w) & \forall v,w\in V\\
& f_{vw} = -f_{wv} & \forall v,w\in V\\
& \sum_{w\in V} f_{vw} = 0 & \forall v\in V\setminus\{s,t\}\\
\end{array}
\]

Inny przykład: znajdowanie długości najkrótszej ścieżki od \(s\) do \(t\) w grafie \(G=(V,E)\) z funkcją \(w:E\rightarrow\mathbb{R}\) opisującą długości krawędzi. Tu dla każdego wierzchołka \(v\) mamy zmienną \(d_v\). Dla wierzchołków na najkrótszej ścieżce od \(s\) do \(t\) zmienna \(d_v\) będzie odpowiadać odległości od \(s\) do \(v\).

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zmaksymalizuj} & d_t \\
\textrm{z zachowaniem warunków} & d_v \le d_u + w(u,v) & \forall (u,v)\in E\\
& d_s = 0 &
\end{array}
\]

Zadanie. Udowodnij, że ten program faktycznie jest równoważny problemowi najkrótszej ścieżki.

Terminologia i notacja

Rozważmy PL o \(n\) zmiennych \(x_1,\ldots,x_n\). Wartości zmiennych możemy utożsamiać z wektorem \(\mathbf{x}=(x_1,\ldots,x_n)\in \mathbb{R}^n\). Zauważmy też, że liniową funkcję celu \(\sum_{j=1}^n c_j x_j\) możemy zapisać krócej jako \(\mathbf{c}^T\mathbf{x}\) dla wektora \(\mathbf{c}=(c_1,\ldots,c_n)\). Będziemy utożsamiać tę funkcję z wektorem \(\mathbf{c}\).

  • \(\mathbf{x}\in \mathbb{R}^n\) jest rozwiązaniem dopuszczalnym PL gdy \(\mathbf{x}\) spełnia wszystkie ograniczenia.
  • \(\mathbf{x}\in \mathbb{R}^n\) jest rozwiązaniem optymalnym PL gdy \(\mathbf{x}\) jest rozwiązaniem dopuszczalnym i optymalizuje funkcję celu, tzn. jeśli dla dowolnego dopuszczalnego \(\mathbf{y}\in\mathbb{R}^n\) jest \(\mathbf{c}^T\mathbf{x} \le \mathbf{c}^T\mathbf{y}\) w przypadku gdy PL jest minimalizacyjny (\(\mathbf{c}^T\mathbf{x} \ge \mathbf{c}^T\mathbf{y}\) gdy maksymalizacyjny).
  • PL jest dopuszczalny gdy istnieje rozwiązanie dopuszczalne, w przeciwnym przypadku jest sprzeczny.
  • PL jest nieograniczony gdy jest dopuszczalny ale nie ma rozwiązań optymalnych, tzn. dla PL minimalizacyjnego dla dowolnego \(\lambda \in \mathbb{R}\) istnieje rozwiązanie dopuszczalne \(\mathbf{x}\) takie, że \(\mathbf{c}^T\mathbf{x}<\lambda\).

Będziemy posługiwać się trzema wygodnymi postaciami programów liniowych: kanoniczną, standardową i dopełnieniową.

Postać kanoniczna

Postać kanoniczna jest najbardziej ogólna z trzech rozważanych tu postaci. Program w postaci kanonicznej wygląda następująco:

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zmaksymalizuj} & \sum_{j=1}^n c_j x_j \\
\textrm{z zachowaniem warunków} & \sum_{j=1}^n a_{ij} x_j \le b_i & \forall i=1,\ldots,m
\end{array}
\]
gdzie \(\{a_{ij}, b_i, c_j\}\) są dane. Wygodniej będzie nam używać zapisu macierzowego:

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zmaksymalizuj} & \mathbf{c}^T\mathbf{x} & \\
\textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \le \mathbf{b}&
\end{array}
\]

gdzie \(\mathbf{c}\in\mathbb{R}^n, \mathbf{b}\in\mathbb{R}^m, \mathbf{A}\in M_{m\times n}[\mathbb{R}]\).

Lemat 1
Każdy PL można sprowadzić do postaci kanonicznej.

Dowód
Zadanie min \(\mathbf{c}^T\mathbf{x}\) zamieniamy na max \((-\mathbf{c})^T\mathbf{x}\). Równości \(\mathbf{a}_i^T\mathbf{x} = b_i\) zamieniamy na parę \(\mathbf{a}_i^T\mathbf{x}\le b_i\), \(\mathbf{a}_i^T\mathbf{x}\ge b_i\). Nierówności \(\mathbf{a}_i^T\mathbf{x}\ge b_i\) zamieniamy na \((-\mathbf{a}_i)^T\mathbf{x}\le -b_i\). ♦

Postać standardowa

Programy liniowe modelujące problemy algorytmiczne bardzo często są w następującej postaci (jest to szczególny przypadek postaci kanonicznej):

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zmaksymalizuj} & \mathbf{c}^T\mathbf{x} & \\
\textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \le \mathbf{b}&\\
& \mathbf{x} \ge \mathbf{0}&
\end{array}
\]

Lemat 2
Każdy PL można sprowadzić do postaci standardowej.

Dowód
Możemy założyć, że mamy już PL w postaci kanonicznej. Aby zapewnić nieujemność każdej zmiennej, dla każdej zmiennej \(x_i\) wprowadzamy parę zmiennych \(x_i^+\) i \(x_i^-\) i każde wystąpienie \(x_i\) w PL (także w funkcji celu) zastępujemy przez \(x_i^+-x_i^-\). Dodajemy też \(x_i^+\ge 0\) i \(x_i^-\ge 0\). ♦

Czasem nie chcemy sztucznie zamieniać problemu minimalizacji na maksymalizację przez odwrócenie funkcji celu. Postać standardowa w wersji minimalizacyjnej wygląda następująco:

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zminimalizuj} & \mathbf{c}^T\mathbf{x} & \\
\textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \ge \mathbf{b}&\\
& \mathbf{x} \ge \mathbf{0}&
\end{array}
\]

Postać dopełnieniowa

Postać dopełnieniowa jest najczęściej używana w algorytmach rozwiązujących problem programowania liniowego. Program w tej postaci (w wersji minimalizacyjnej) wygląda następująco:

\[
\begin{array}{ll@{\hspace{15mm}}l}
\textrm{zminimalizuj} & \mathbf{c}^T\mathbf{x} & \\
\textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} = \mathbf{b}& \forall i=1,\ldots,m\\
& \mathbf{x} \ge \mathbf{0}
\end{array}
\]
gdzie \(\mathbf{c}\in\mathbb{R}^n, \mathbf{b}\in\mathbb{R}^m, \mathbf{A}\in M_{m\times n}[\mathbb{R}]\).

Zauważmy, że z dokładnością do zamiany równości na pary nierówności możemy powiedzieć, że PL w postaci dopełnieniowej jest w postaci standardowej.

Lemat 3
Każdy PL można sprowadzić do postaci dopełnieniowej.

Dowód
Możemy założyć, że mamy już PL w postaci standardowej. Dla każdej nierówności \(\mathbf{a}_i^T\mathbf{x}\le b_i\) wprowadzamy ,,zmienną dopełnieniową'' \(s_i\) i nierówność zastępujemy przez \(\mathbf{a}_i^T\mathbf{x}-s_i=b_i\) oraz \(s_i\ge 0\). ♦

Geometria programów liniowych

Przypomnijmy:

  • zbiór punktów spełniających \(\mathbf{a}^T\mathbf{x}=b\), \(\mathbf{a}\in\mathbb{R}^n, \mathbf{b}\in\mathbb{R}\) to hiperpłaszczyzna,
  • zbiór punktów spełniających \(\mathbf{a}^T\mathbf{x}\ge b\) to półprzestrzeń,
  • zbiór rozwiązań dopuszczalnych PL w postaci kanonicznej \(\mathbf{A}\mathbf{x}\le b\) to przecięcie półprzestrzeni, czyli wielościan.

Dla przykładu, zbiór rozwiązań dopuszczalnych PL z Przykładu 1 jest czworokątem (w 2 wymiarach hiperpłaszczyzny to proste, półprzestrzenie to półpłaszczyzny, a wielościany to wielokąty). Zauważmy, że rozwiązanie optymalne to najdalszy punkt wielościanu w kierunku wektora funkcji celu (dla problemu maksymalizacyjnego; dla minimalizacyjnego w kierunku wektora odwrotnego).

Zauważmy, że zbiór rozwiązań PL jest przecięciem zbiorów wypukłych (półprzestrzeni, hiperpłaszczyzn) a więc jest wypukły.

Struktura rozwiązań optymalnych

Zdefiniujemy teraz trzy naturalne pojęcia związane z programami liniowymi i ich geometryczną interpretacją. Za chwilę pokażemy, że są one równoważne.

  • Wierzchołkiem wielościanu \(\mathcal{P}\) nazywamy dowolny punkt \(\mathbf{x}\in \mathcal{P}\), który jest jedynym rozwiązaniem optymalnym dla pewnej funkcji celu \(\mathbf{c}\).
  • Punktem ekstremalnym wielościanu \(\mathcal{P}\) nazywamy dowolny punkt \(\mathbf{x}\in \mathcal{P}\), który nie jest wypukłą kombinacją dwóch innych punktów \(\mathbf{y},\mathbf{z}\in\mathcal{P}\). (Przypomnijmy, że wypukła kombinacja \(\mathbf{y}\) i \(\mathbf{z}\) to dowolny punkt postaci \(\lambda\mathbf{y}+(1-\lambda)\mathbf{z}\) dla pewnego \(\lambda\in[0,1]\).)
  • Bazowe rozwiązanie dopuszczalne (brd) PL o \(n\) zmiennych to rozwiązanie dopuszczalne \(\mathbf{x}\in\mathbb{R}^n\) takie, że istnieje \(n\) liniowo niezależnych ograniczeń (w sensie wektorów współczynników przy \(x_i\), tzn. \(x_1+2x_2\ge 1\) i \(x_1+2x_2\le 2\) są liniowo zależne), które dla \(\mathbf{x}\) są spełnione z równością. Na przykład \((0,0)\) i \((\frac{2}{3},\frac{8}{3})\) są brd programu z Przykładu 1.

W poniższych lematach rozważamy dowolny program liniowy oznaczamy przez \(\mathcal{P}\) wielościan jego rozwiązań dopuszczalnych. Zakładamy, że jest on dany w postaci \(\max \mathbf{c}^T\mathbf{x}\), \(\mathbf{A}\mathbf{x}\le \mathbf{b}\) (sprowadzenie dowolnego programu do takiej postaci nie zmienia wielościanu rozwiązań dopuszczalnych).

Lemat 4
Jeśli \(\mathbf{x}\) jest wierzchołkiem \(\mathcal{P}\) to \(\mathbf{x}\) jest punktem ekstremalnym.

Dowód
Niech \(\mathbf{c}\) będzie funkcją celu taką, że \(\mathbf{x}\) jest jedynym rozwiązaniem optymalnym LP dla funkcji celu \(\mathbf{c}\).

Załóżmy, że \(\mathbf{x} = \lambda \mathbf{y} + (1-\lambda)\mathbf{z}\) dla pewnych \(\mathbf{y},\mathbf{z}\in\mathcal{P}\) oraz \(\lambda\in[0,1]\). Ponieważ \(\mathbf{x}\) jest jedyny optymalny więc \(\mathbf{c}^T\mathbf{y},\mathbf{c}^T\mathbf{z} < \mathbf{c}^T\mathbf{x}\).
Ale wówczas, z liniowości \(\mathbf{c}\)
\[\mathbf{c}^T\mathbf{x}=\lambda\mathbf{c}^T\mathbf{y} + (1-\lambda)\mathbf{c}^T\mathbf{z} < \lambda\mathbf{c}^T\mathbf{x} + (1-\lambda)\mathbf{c}^T\mathbf{x} = \mathbf{c}^T\mathbf{x}.\]

Lemat 5
Jeśli \(\mathbf{x}\) jest punktem ekstremalnym \(\mathcal{P}\) to \(\mathbf{x}\) jest bazowym rozwiązaniem dopuszczalnym.

Dowód
Z definicji punktu ekstremalnego \(\mathbf{x}\) jest dopuszczalny. Załóżmy, że nie jest brd, tzn. że nie istnieje \(n\) liniowo niezależnych ograniczeń spełnionych z równością dla \(\mathbf{x}\). Intuicyjnie, oznacza to, że wokół \(\mathbf{x}\) jest nieco ,,luzu'', tzn. jest pewien wektor \(\mathbf{d}\) (liniowo niezależny od wektorów ograniczeń spełnionych z równością), wzdłuż którego możemy się przemieszczać z \(\mathbf{x}\) w przód i w tył pozostając w \(\mathcal{P}\). Istotnie, pokażemy, że \(\mathbf{x}\) jest kombinacją wypukłą dwóch punktów w \(\mathcal{P}\).

Niech \(T=\{i\ |\ \mathbf{a}_i^T\mathbf{x}=b_i\}\). Wiemy, że \(\{\mathbf{a}_i\ |\ i\in T\}\) nie rozpina \(\mathbb{R}^n\), tzn. \(\mathrm{rank}[a_i, i\in T]0\), \(x\pm \epsilon \mathbf{d}\in \mathcal{P}\), czyli będzie sprzeczność (bo \(\mathbf{x}\) jest ekstremalny). Istotnie, jeśli \(i\in T\) to dla dowolnego \(\epsilon\), \(\mathbf{a}_i^T(\mathbf{x}\pm \epsilon \mathbf{d})=b_i\). Dla \(i\not \in T\) mamy \(\mathbf{a}_i^T\mathbf{x}>b_i\), a więc na pewno można wybrać dostatecznie małe \(\epsilon\), żeby \(\mathbf{a}_i^T(\mathbf{x}\pm\epsilon\mathbf{d})\ge b_i\) dla każdego \(i\not \in T\). ♦

Lemat 6
Jeśli \(\mathbf{x}\) jest bazowym rozwiązaniem dopuszczalnym PL to \(\mathbf{x}\) jest wierzchołkiem \(\mathcal{P}\).

Dowód
Niech \(T=\{i\ |\ \mathbf{a}_i^T\mathbf{x}=b_i\}\). Podamy funkcję celu \(\mathbf{c}\), przy której \(\mathbf{x}\) jest jedynym rozwiązaniem optymalnym: \(\mathbf{c}=\sum_{i\in T}\mathbf{a}_i\). Dla dowolnego \(\mathbf{y}\in\mathcal{P}\) mamy
\[\mathbf{c}^T\mathbf{y}=\sum_{i\in T}\mathbf{a}_i^T\mathbf{y} \le \sum_{i\in T}b_i = \mathbf{c}^T\mathbf{x},\]
(czyli \(\mathbf{x}\) jest rozwiązaniem optymalnym) przy czym równość zachodzi tylko gdy dla każdego \(i\in T\), \(\mathbf{a}_i^T\mathbf{y}=b_i\), a to jest układ którego jedynym rozwiązaniem jest \(x\) (bo \(\mathrm{rank}[\mathbf{a}_i\ |\ i\in T] = n\)). ♦

Wniosek 7
Dla dowolnego programu liniowego są równoważne:

  • \(\mathbf{x}\) jest wierzchołkiem,
  • \(\mathbf{x}\) jest punktem ekstremalnym,
  • \(\mathbf{x}\) jest bazowym rozwiązaniem dopuszczalnym.


Twierdzenie 8
Każdy ograniczony PL w postaci standardowej \(\max \mathbf{c}^T\mathbf{x}\), \(\mathbf{A}\mathbf{x}\le\mathbf{b}\), \(\mathbf{x}\ge\mathbf{0}\) ma rozwiązanie optymalne, które jest punktem ekstremalnym.

Dowód
Niech \(\mathbf{x}\) będzie rozwiązaniem optymalnym PL. Jeśli \(\mathbf{x}\) jest ekstremalny -- koniec. W przeciwnym przypadku pokażemy jak przejść od \(\mathbf{x}\) do punktu ekstremalnego o tej samej wartości funkcji celu (używając analogii trójwymiarowej, przesuniemy się z \(\mathbf{x}\) wewnątrz wielościanu do ściany wielościanu, następnie ze środka ściany do krawędzi, a z krawędzi do wierzchołka).

Oznaczmy przez \(\mathcal{P}\) wielościan rozwiązań dopuszczalnych. Skoro \(\mathbf{x}\) nie jest punktem ekstremalnym, to istnieje \(\mathbf{y}\ne \mathbf{0}\) taki że \(\mathbf{x}+\mathbf{y},\mathbf{x}-\mathbf{y}\in\mathcal{P}\).

Po pierwsze zauważmy, że przesuwając się wzdłuż wektora \(\mathbf{y}\) nie zmieniamy wartości funkcji celu. Istotnie, gdyby \(\mathbf{c}^T \mathbf{y} > \mathbf{0}\) to \(\mathbf{c}^T (\mathbf{x}+\mathbf{y}) > \mathbf{c}^T \mathbf{x}\), natomiast gdyby \(\mathbf{c}^T \mathbf{y} < \mathbf{0}\) to \(\mathbf{c}^T (\mathbf{x}-\mathbf{y}) > \mathbf{c}^T \mathbf{x}\), czyli w obu przypadkach dostajemy sprzeczność z założeniem że \(\mathbf{x}\) jest ekstremalny (a więc jest wierzchołkiem). Skoro \(\mathbf{c}^T\mathbf{y} = \mathbf{0}\) to dla dowolnego \(\alpha\), \(\mathbf{c}^T(\mathbf{x}+\alpha\mathbf{y})=\mathbf{c}^T\mathbf{x}\).

Rozważmy teraz dowolne ograniczenie spełnione z równością, czyli \((\mathbf{a}_i)^T\mathbf{x} = \mathbf{b}_i\). Teraz zauważmy, że skoro \((\mathbf{a}_i)^T (\mathbf{x}+\mathbf{y}) \le \mathbf{b}\) to \((\mathbf{a}_i)^T \mathbf{y} \le 0\). Podobnie skoro \((\mathbf{a}_i)^T (\mathbf{x}-\mathbf{y}) \le \mathbf{b}\) to \((\mathbf{a}_i)^T \mathbf{y} \ge 0\). Stąd \((\mathbf{a}_i)^T\mathbf{y} = \mathbf{0}\) a nawet \((\mathbf{a}_i)^T(\alpha\mathbf{y}) = \mathbf{0}\) dla dowolnego \(\alpha\in\mathbb{R}\). Czyli jeśli \(i\)-te ograniczenie jest spełnione z równością, to po przesunięciu się wzdłuż \(\mathbf{y}\) dalej tak będzie, tzn.\ \((\mathbf{a}_i)^T(\mathbf{x}+\alpha \mathbf{y}) = \mathbf{b}_i\).
Geometrycznie, odpowiada to spostrzeżeniu, że jeśli \(\mathbf{x}\) jest na krawędzi (ścianie, ...), to posuwając się wzdłuż \(\mathbf{y}\) dalej pozostajemy na krawędzi (ścianie, ...).

Niech teraz \(\lambda = \max\{\alpha\ |\ \mathbf{x} + \alpha \mathbf{y} \in \mathcal{P} \text{ oraz } \mathbf{x} - \alpha \mathbf{y} \in \mathcal{P} \}\). Weźmy \(j\) takie, że \(y_j \ne 0\). Zauważmy, że jeśli \(x_j = 0\) to \(y_j = 0\) (wpp. \(x_j+y_j<0\) lub \(x_j-y_j<0\), czyli \(\mathbf{x}+\mathbf{y}\not\in\mathcal{P}\) lub \(\mathbf{x}-\mathbf{y}\not\in\mathcal{P}\)), a więc musi być \(x_j \ne 0\). Oznacza to, że dla dostatecznie dużego \(\alpha\), \(x_j + \alpha y_j = 0\) lub \(x_j - \alpha y_j = 0\), czyli \(\lambda\) jest dobrze określone. Zauważmy, że w rozwiązaniu dopuszczalnym \(\mathbf{x}+\lambda \mathbf{y}\) rośnie liczba ograniczeń spełnionych z równością. To implikuje, że powyższą operację wykonamy co najwyżej \(n+m\) razy zanim dojdziemy do punktu ekstremalnego. ♦

Wniosek 9
Istnieje algorytm (siłowy), który rozwiązuje PL w postaci standardowej o \(n\) zmiennych i \(m\) ograniczeniach w czasie \(O({m\choose n}n^3)\).

Dowód
Żeby rozwiązać LP w postaci standardowej wystarczy sprawdzić wszystkie wierzchołki wielościanu i wybrać wierzchołek o najmniejszej wartości funkcji celu. Wierzchołków jest tyle co bazowych rozwiązań dopuszczalnych, czyli \(m\choose n\). Aby znaleźć taki wierzchołek wystarczy rozwiązać układ odpowiednich \(n\) liniowo niezależnych równań. ♦

(Uwaga. W powyższym twierdzeniu \(m\) oznacza liczbę ograniczeń, a nie liczbę wierszy macierzy \(\mathbf{A}\). Jeśli tę liczbę wierszy oznaczymy przez \(r\) to \(m=r+n\)).

Podsumowując, nasze rozważania geometryczne pozwoliły nam zmienić ,,naturę problemu'': w pewnym sensie przeszliśmy od problemu o charakterze ciągłym (continuum rozwiązań dopuszczalnych) do problemu dyskretnego.

Programowanie liniowe 2: Algorytm simplex i informacja o algorytmach wielomianowych

Algorytmy programowania liniowego

Pierwszym algorytmem programowania liniowego był algorytm simplex, opublikowany przez George'a Dantziga w 1947 roku. Algorytm ten najpierw znajduje pewien wierzchołek wielościanu rozwiązań dopuszczalnych, a następnie w pętli przemieszcza się wzdłuż krawędzi do jednego z sąsiednich wierzchołków tak, aby poprawić wartość funkcji celu. Niestety okazuje się, że jego pesymistyczna złożoność jest wykładnicza. Doskonale zachowuje się on jednak dla ,,rzeczywistych'' danych i jest powszechnie stosowany w praktyce.

Kolejny przełom nastąpił w 1979 kiedy to Leonid Khachiyan opublikował tzw. metodę elipsoidalną, czyli algorytm wielomianowy o złożoności \(O(n^4L)\), gdzie \(L\) jest ograniczone z góry przez długość zapisu binarnego danych (macierzy \(\mathbf{A}\), wektorów \(\mathbf{b}\) i \(\mathbf{c}\)). Istnieją implementacje tego algorytmu, jednakże źle sprawdzają się w praktyce.

W 1984 roku zupełnie inne podejście zaproponował Narendra Karmarkar. Jego metoda punktu wewnętrznego osiąga złożoność \(O(n^{3.5}L)\) i została poprawiona przez kolejnych autorów do \(O(n^3L)\). Podobno niektóre implementacje tego algorytmu zachowują się bardzo dobrze dla rzeczywistych danych (porównywalnie albo lepiej niż algorytm simplex). Mimo to, nawet ten algorytm jest rzadko stosowany praktyce.

Algorytm simplex

W poprzednim rozdziale zauważyliśmy już, że poszukując rozwiązań optymalnych można ograniczyć się do wierzchołków wielościanu. Algorytm simplex korzysta z tej obserwacji i realizuje podejście local search. Dokładniej, algorytm zaczyna od dowolnego wierzchołka wielościanu i w każdej kolejnej iteracji próbuje przemieścić się do takiego sąsiedniego wierzchołka, że wartość funkcji celu poprawia się (lub przynajmniej nie pogarsza).

Opiszemy teraz algorytm simplex na przykładzie konkretnego programu liniowego:

\[
\begin{array}{lr}
\textrm{max} & 3x_1 + x_2 +2x_3 \\
& x_1 + x_2 + 3x_3 \le 30 \\
& 2x_1 + 2x_2 + 5x_3 \le 24\\
& 4x_1 + x_2 + 2 x_3 \le 36\\
& x_1, x_2, x_3 \ge 0.
\end{array}
\]

Zauważmy, że jest to program w postaci standardowej (w wersji maksymalizacyjnej), oraz wszystkie wyrazy wolne z prawych stron nierówności są nieujemne. Dzięki temu łatwo znaleźć dla niego rozwiązanie dopuszczalne --- rozwiązanie zerowe: \(x_1=0\), \(x_2=0\), \(x_3=0\).

Zapiszmy teraz ten program w postaci dopełnieniowej, wprowadzając zmienną dopełnieniową dla każdej nierówności (z wyłączeniem warunków nieujemnościowych). Dodatkowo funkcję celu zastąpmy nową zmienną \(z\):

\[
\begin{array}{rl}
\textrm{max} & z \\
z & = 3x_1 + x_2 +2x_3\\
x_4 & = 30 - x_1 - x_2 - 3x_3 \\
x_5 & = 24 - 2x_1 - 2x_2 - 5x_3\\
x_6 & = 36 - 4x_1 - x_2 - 2 x_3\\
& x_1, x_2, x_3, x_4, x_5, x_6 \ge 0.
\end{array}
\]

Nasze rozwiązanie dopuszczalne, rozszerzone o zmienne dopełnieniowe ma teraz następującą postać:
\[ x_1 = 0, x_2 = 0, x_3 = 0, \quad x_4 = 30, x_5 = 24, x_6 = 36.\]

Podczas działania algorytmu, w kolejnych krokach będziemy zmieniać nasz program liniowy. Mimo, iż ograniczenia będą się zmieniać, zawsze będą one opisywać ten sam wielościan (tzn. zbiór rozwiązań dopuszczalnych). W każdym kroku nasz program liniowy będzie miał szczególną postać, która w sposób jednoznaczny będzie wyznaczać pewien wierzchołek wielościanu --- najlepsze dotąd znalezione rozwiązanie. Podamy teraz niezmiennik, który opisuje postać tych programów.

Niezmiennik sformułujemy dla dowolnego programu (a nie tylko powyższego przykładu). Załóżmy, że początkowy program w postaci dopełnieniowej ma \(m\) równości, oraz zawiera zmienne \(x_1,\ldots, x_{n+m}\) (gdzie \(x_{n+1},\ldots, x_{n+m}\) są zmiennymi dopełnieniowymi).

Niezmiennik 1
Zbiór zmiennych \(\{x_1,\ldots,x_{n+m}\}\) dzieli się na dwa rozłączne zbiory: \(m\) zmiennych bazowych i \(n\) zmiennych niebazowych. Oznaczmy zbiór indeksów zmiennych bazowych przez \(B=\{B_1,\ldots,B_m\}\) (baza) i zmiennych niebazowych przez \(N=\{N_1,\ldots,N_n\}\). Program zawiera:

  • równanie postaci \(z = v + \sum_{j=1}^n c_j x_{N_j}\);
  • dla każdego \(i=1,\ldots,m\) równanie postaci \(x_{B_i} = b_i + \sum_{j=1}^{n}a_{i,j} x_{N_j}\), gdzie \(b_i\ge 0\);
  • dla każdego \(i=1,\ldots,n+m\) nierówność \(x_i\ge 0\),

gdzie \(v\), \(c_j\), \(b_j\), \(a_{i,j}\) są stałymi.

W rozważanym przez nas przykładzie, powyższy niezmiennik jest spełniony dla \(N=\{1,2,3\}\) oraz \(B=\{4,5,6\}\).

Fakt
Jeśli spełniony jest niezmiennik 1, to rozwiązanie \((x_1,\ldots,x_{n+m})\) postaci
\[x_i = \left\{ \begin{array}{cl}
0 & \text{gdy \(i\in N\)} \\
b_j & \text{gdy \(i=B_j\) dla pewnego \(j=1,\ldots,n\)}
\end{array} \right.
\]
jest bazowym rozwiązaniem dopuszczalnym o wartości funkcji celu \(v\).

Dowód
Łatwo sprawdzić, że tak zdefiniowane rozwiązanie jest rozwiązaniem dopuszczalnym o wartości funkcji celu \(v\). Aby pokazać, że jest to bazowe rozwiązanie dopuszczalne musimy wskazać \(n+m\) liniowo niezależnych ograniczeń spełnionych z równością. W tym celu, dla każdego \(i\in B\) wybieramy (jedyną) równość zawierającą \(x_i\) oraz dla każdego \(i\in N\) nierówność \(x_i\ge 0\). ♦

Z powyższego faktu wynika, że o ile spełniony jest niezmiennik, to faktycznie znajdujemy się w wierzchołku aktualnego programu w postaci dopełnieniowej. Łatwo sprawdzić też, że po zignorowaniu zmiennych dopełnieniowych otrzymamy wierzchołek odpowiadającego programu w postaci standardowej, a więc faktycznie algorytm będzie generował wierzchołki wielościanu oryginalnego programu liniowego.

Wróćmy do algorytmu simplex. Naszym celem jest zwiększenie zmiennej \(z\). W tym celu spójrzmy na dowolną zmienną niebazową z dodatnim współczynnikiem w funkcji celu. W tym przypadku możemy wybrać dowolną zmienną z \(x_1, x_2, x_3\) --- wybierzmy \(x_1\).
Oczywiście powiększając \(x_1\) powiększamy \(z\). Jak bardzo możemy powiększyć \(x_1\), zachowując wszystkie ograniczenia? Dopóki zmienne bazowe pozostają nieujemne, a więc:
\[x_1 := \min \left\{\frac{30}{1}, \frac{24}{2}, \frac{36}{4}\right\} = \frac{36}{4} = 9. \]
Po takiej operacji przynajmniej jedna zmienna bazowa (w tym przypadku \(x_6\)) przyjmuje wartość \(0\). To pozwala na zmianę bazy (a w konsekwencji zmianę wierzchołka, w którym jesteśmy): zmienna \(x_1\) wchodzi do bazy (jest zmienną wchodzącą), a zmienna \(x_6\) wychodzi z bazy (zmienna wychodząca). Operacja wymiany bazy (ang. {\mathbf{b}f pivot}) przebiega w dwóch krokach:

  1. Rozwiąż równanie zawierające zmienną wychodzącą ze względu na zmienną wchodzącą. W tym przypadku otrzymujemy:
    \[x_1 = 9 - \frac{1}{4}x_2 - \frac{1}{2}x_3 - \frac{1}{4}x_6. \]
  2. wstaw wynik zamiast \(x_1\) z prawej strony wszystkich równań (czyli uaktualnij współczynniki przy zmiennych niebazowych i wyrazy wolne). W tym przypadku otrzymujemy:
    \[
    \begin{array}{rlccccccc}
    z & = & 27 &+& \frac{1}{4}x_2 &+& \frac{1}{2}x_3 &-& \frac{3}{4}x_6\\
    x_1 & = & 9 &-& \frac{1}{4}x_2 &-& \frac{1}{2}x_3 &-& \frac{1}{4}x_6\\
    x_4 & = & 21 &-& \frac{3}{4}x_2 &-& \frac{5}{2}x_3 &+& \frac{1}{4}x_6\\
    x_5 & = & 6 &-& \frac{3}{2}x_2 &-& 4x_3 &+& \frac{1}{2}x_6.\\
    \end{array}
    \]

Fakt
Po operacji wymiany bazy otrzymujemy program liniowy o tym samym zbiorze rozwiązań dopuszczalnych.

Otrzymaliśmy rozwiązanie \((9,0,0,21,6,0)\) o wartości funkcji celu \(27\). Wykonajmy kolejną operację wymiany bazy. Teraz jako zmienną wchodzącą możemy wybrać już tylko jedną z dwóch: \(x_2\) lub \(x_3\), bo tylko te zmienne mają dodatnie współczynniki w funkcji celu. Wybierzmy \(x_3\). Podobnie jak poprzednio:
\[x_3 := \min \left\{\frac{9}{\frac{1}{2}}, \frac{21}{\frac{5}{2}}, \frac{6}{4}\right\} = \frac{6}{4} = \frac{3}{2}.\]
A więc \(x_5\) wychodzi z bazy. Otrzymujemy nowy program:
\[
\begin{array}{rlccccccc}
z & = & \frac{111}{4} &+& \frac{1}{16}x_2 &-& \frac{1}{8}x_5 &-& \frac{11}{16}x_6\\
x_1 & = & \frac{33}{4} &-& \frac{1}{16}x_2 &+& \frac{1}{8}x_5 &-& \frac{5}{16}x_6\\
x_3 & = & \frac{3}{2} &-& \frac{3}{8}x_2 &-& \frac{1}{4}x_5 &+& \frac{1}{8}x_6\\
x_4 & = & \frac{69}{4} &+& \frac{3}{16}x_2 &+& \frac{5}{8}x_5 &-& \frac{1}{16}x_6.\\
\end{array}
\]

Teraz jedynym kandydatem do wejścia do bazy jest \(x_2\). Zauważmy, że w ostatnim równaniu współczynnik przed \(x_2\) jest dodatni. Oznacza to, że zwiększając \(x_2\) możemy też zwiększać \(x_4\) zachowując ostatnią równość zawsze spełnioną. A więc przy wyborze zmiennej wychodzącej nie bierzemy pod uwagę \(x_4\):
\[x_2 := \min \left\{\frac{\frac{33}{4}}{\frac{1}{16}}, \frac{\frac{3}{2}}{\frac{3}{8}}\right\} = \frac{\frac{3}{2}}{\frac{3}{8}} = 4.\]

Uwaga
Zastanówmy się jednak przez chwilę, co by było, gdybyśmy nie mieli czego wziąć do minimum, tzn. gdyby istniała zmienna z dodatnim współczynnikiem w funkcji celu i nieujemnymi współczynnikami w pozostałych równaniach? Wtedy powiększając tę zmienną moglibyśmy otrzymać rozwiązanie dopuszczalne o dowolnie wysokiej wartości funkcji celu. W takiej sytuacji algorytm simplex zwraca komunikat ``PROGRAM NIEOGRANICZONY'' i kończy działanie.

Wracając do naszego programu liniowego, otrzymujemy:

\[
\begin{array}{rlccccccc}
z & = & 28 &-& \frac{1}{6}x_3 &-& \frac{1}{6}x_5 &-& \frac{2}{3}x_6\\
x_1 & = & 8 &+& \frac{1}{6}x_3 &+& \frac{1}{6}x_5 &-& \frac{1}{3}x_6\\
x_3 & = & 4 &-& \frac{8}{3}x_3 &-& \frac{2}{3}x_5 &+& \frac{1}{3}x_6\\
x_4 & = & 18 &-& \frac{1}{2}x_3 &+& \frac{1}{2}x_5 &+& 0x_6.\\
\end{array}
\]

Otrzymaliśmy więc sytuację, gdy nie możemy wykonać operacji wymiany bazy ponieważ wszystkie współczynniki w funkcji celu są ujemne. Jest jednak jasne, że musieliśmy w ten sposób dostać rozwiązanie optymalne programu, ponieważ dla dowolnych nieujemnych wartości zmiennych wartość funkcji celu naszego programu nie może przekroczyć aktualnej, czyli \(28\). Ponieważ w kolejnych krokach algorytmu otrzymywaliśmy równoważne programy liniowe o tym samym zbiorze rozwiązań dopuszczalnych, jest to także rozwiązanie optymalne oryginalnego programu.Odnotujmy te rozważania jako fakt:

Fakt
Jeśli w pewnym kroku algorytmu simplex wszystkie współczynniki (przy zmiennych niebazowych) w funkcji celu są ujemne, to znalezione bazowe rozwiązanie dopuszczalne jest optymalnym rozwiązaniem oryginalnego programu.

W naszym przypadku dostaliśmy rozwiązanie \((x_1,x_2,x_3) = (8,4,0)\) o wartości funkcji celu \(28\).

W tej chwili zasada działania algorytmu simplex i jego częściowa poprawność (tzn. poprawność pod warunkiem zatrzymania się programu) powinny być już jasne. Zajmijmy się jeszcze przez chwilę właśnie kwestią warunku stopu i złożoności obliczeniowej.

Pseudokod algorytmu simplex

Podsumujmy teraz powyższe rozważania podając pseudokod algorytmu Simplex. Stosujemy oznaczenia takie jak w niezmienniku 1.

  1. Sprowadź PL do postaci dopełnieniowej.
  2. Znajdź równoważny PL taki, żeby spełniony był niezmiennik 1.
  3. Dopóki istnieje \(j\in \{1,\ldots,n\}\) takie, że \(c_j > 0\) (\(c_j=\) współczynnik przed \(x_{N_j}\) w aktualnej funkcji celu),
    1. Wybierz takie \(j\) (\(x_{N_j}\) jest zmienną wchodzącą).
    2. Jeśli dla każdego \(i=1,\ldots,m\), jest \(a_{i,j} \ge 0\) (tzn.\ dla każdego równania współczynnik przed \(x_{N_j}\) jest nieujemny) zwróć ,,PROGRAM NIEOGRANICZONY''.
    3. wpp., wybierz \(i\) takie, że \(\frac{b_i}{-a_{i,j}} = \min\{\frac{b_i}{-a_{i,j}}\ |\ a_{i,j}<0\}\) (\(x_{B_i}\) jest zmienną wychodzącą).
    4. wykonaj operację Pivot (\(j\),\(i\))
  4. Zwróć rozwiązanie postaci: dla każdego \(i=1,\ldots,n\),
    \[x_i = \left\{ \begin{array}{cl}
    0 & \text{gdy \(i\in N\)} \\
    b_j & \text{gdy \(i=B_j\) dla pewnego \(j=1,\ldots,m\).}
    \end{array} \right.
    \]

Podamy teraz pseudokod operacji Pivot(\(in\),\(out\)), realizującej usunięcie z bazy \(x_{B_{out}}\) i dodanie do niej \(x_{N_{in}}\). Przy implementacji, wygodnie jest przechowywać wszystkie stałe w jednej tablicy \(a_{i,j}\), gdzie \(i\in\{0,\ldots,m\}\), \(j\in\{0,\ldots,n\}\), oraz przyjmujemy że \(a_{0,0}=v\), \(a_{0,j}=c_j\) dla \(j=1,\ldots,n\) oraz \(a_{i,0}=b_i\) dla \(i=1,\ldots,m\) (wartości \(a_{i,j}\) dla \(i,j\ge 1\) mają takie same znaczenie jak wcześniej).

Pseudokod operacji pivot
Pivot (\(in\),\(out\))

Warunek stopu i reguła Blanda

Jest jasne, że jeśli przy kolejnych operacjach wymiany bazy zawsze otrzymujemy większą (lub, w przypadku minimalizacji, mniejszą) wartość funkcji celu to algorytm musi się zakończyć --- po prostu dlatego, że jest ograniczona liczba wierzchołków wielościanu. Poprzednik tej implikacji niestety nie zawsze jest jednak spełniony. Dla przykładu, rozważmy następujący program:

\[
\begin{array}{rlccccccc}
z & = & 4 &+& 2x_1 &-& x_2 &-& 4x_4\\
x_3 & = & \frac{1}{2} & & & & & - & \frac{1}{2}x_4\\
x_5 & = & &-& 2x_1 &+& 4x_2 &+& 3x_4\\
x_6 & = & &+& x_1 &-& 3x_2 &+& 2x_4.\\
\end{array}
\]

Mamy \(B = \{3,4,6\}\), \(N=\{1,2,4\}\), \(\mathbf{x} = (0, 0, \frac{1}{2}, 0, 0, 0)\) oraz funkcja celu ma wartość \(z=4\). Jako zmienną wchodzącą możemy wybrać jedynie \(x_1\), natomiast jako zmienną wychodzącą jedynie \(x_5\). Po wymianie bazy otrzymujemy:

\[
\begin{array}{rlccccccc}
z & = & 4 &+& 3x_2 &-& x_4 &-& x_5\\
x_1 & = & &+& 2x_2 &+& \frac{3}{2}x_4 &-& \frac{1}{2}x_5\\
x_3 & = & \frac{1}{2} & & & - & \frac{1}{2}x_4 & & \\
x_6 & = & &-& x_2 &+& \frac{7}{2}x_4 &-& \frac{1}{2}x_5.\\
\end{array}
\]

Mamy \(B = \{1,3,6\}\), \(N=\{2,4,5\}\), \(\mathbf{x} = (0, 0, \frac{1}{2}, 0, 0, 0)\) oraz funkcja celu ma wartość \(z=4\). Widzimy, że chociaż zmieniła się baza, bazowe rozwiązanie dopuszczalne pozostało to samo (w szczególności wartość funkcji celu się nie zmieniła). Może być to powodem poważnych kłopotów, a nawet zapętlenia się algorytmu. Początkowo ten problem ignorowano (!), gdyż w praktycznych zastosowaniach pojawia się on niezwykle rzadko. W 1977 (czyli w 30 lat od powstania algorytmu simplex) Robert Bland zaproponował niezwykle prostą heurystykę, o której można pokazać (dowód nie jest bardzo trudny, lecz pominiemy go tutaj), że gwarantuje zakończenie algorytmu.

Twierdzenie [Reguła Blanda]
Jeśli podczas wymiany bazy:

  • spośród możliwych zmiennych wchodzących wybierana jest zmienna o najmniejszym indeksie oraz,
  • spośród możliwych zmiennych wychodzących wybierana jest zmienna o najmniejszym indeksie (zauważmy, że może być wiele możliwych zmiennych wychodzących, gdy w wielu równaniach iloraz wyrazu wolnego i współczynnika przy zmiennej wchodzącej jest taki sam)

to algorytm simplex kończy swoje działanie.

Zapętlenie się algorytmu simplex jest równoważne powrotowi do tej samej bazy. Ponieważ dla programu o \(m\) zmiennych niebazowych i \(n\) zmiennych niebazowych mamy \(O({{n+m}\choose n})\) możliwych baz, więc algorytm simplex z regułą Blanda wykonuje \(O({{n+m}\choose n})\) operacji wymiany bazy (przy każdej z nich wykonuje się \(O(nm)\) operacji arytmetycznych). Z drugiej strony, odnotujmy, że

Fakt
Istnieją przykłady programów liniowych, dla których algorytm simplex działa w czasie \(\Omega(2^n)\).

Mimo to, następujący problem pozostaje otwarty.

Problem
Czy istnieją reguły wyboru zmiennej wchodzącej i wychodzącej, dla których algorytm simplex działa w czasie wielomianowym?

Znajdowanie początkowego bazowego rozwiązania dopuszczalnego

Do wyjaśnienia pozostała jeszcze jedna kwestia. Zakładaliśmy, że program jest postaci \[\max \mathbf{c}^T\mathbf{x}, \mathbf{A}\mathbf{x}\le b, \mathbf{x}\ge 0, \] oraz że \(\mathbf{b}\ge \mathbf{0}\). Wtedy łatwo jest znaleźć równoważny program w postaci dopełnieniowej, który spełnia niezmiennik 1 (innymi słowy: pierwsze bazowe rozwiązanie dopuszczalne). Teraz opiszemy jak to zrobić w przypadku ogólnym. Rozwiązanie będzie dość zaskakujące: żeby znaleźć pierwsze bazowe rozwiązanie dopuszczalne użyjemy algorytmu simplex.

  1. Sprowadź program do postaci dopełnieniowej:

Program (P1)
\[
\begin{array}{rcccccccc}
\textrm{max} & & 0 & + & c_1x_1 & + & \ldots & + & c_nx_n \\
x_{n+1}& = & b_1 & + & a_{11}x_1 & + & \ldots & + & a_{1n}x_n \\
x_{n+2}& = & b_2 & + & a_{21}x_1 & + & \ldots & + & a_{2n}x_n \\
\vdots & & & & & & & \\
x_{n+m}& = & b_m & + & a_{m1}x_1 & + & \ldots & + & a_{mn}x_n \\
x_i &\ge &0
\end{array}
\]

  • Dodaj nową zmienną \(x_0\) i zbuduj nowy program:
  • Program (P2)
    \[
    \begin{array}{rcccccccccc}
    \textrm{min} & & x_0 & & & & & & & & \\
    x_{n+1}& = & b_1 & + & a_{11}x_1 & + & \ldots & + & a_{1n}x_n & + & x_0\\
    x_{n+2}& = & b_2 & + & a_{21}x_1 & + & \ldots & + & a_{2n}x_n & + & x_0\\
    \vdots & & \vdots & & & & & \\
    x_{n+m}& = & b_m & + & a_{m1}x_1 & + & \ldots & + & a_{mn}x_n & + & x_0\\
    x_i &\ge &0
    \end{array}
    \]

  • Przyjmij \(N=\{0,\ldots, n\}\), oraz \(B=\{n+1,\ldots,n+m\}\). Powyższy PL prawie spełnia niezmiennik, brakuje ,,tylko'' warunku ,,\(b_i\ge 0\) dla każdego \(i\)''.
  • Wybierz \(k\) takie, że \(b_k = \min_i \{b_i\}\). Za pomocą operacji Pivot, usuń z bazy \(x_k\) i wprowadź do bazy \(x_0\). Otrzymujemy nowy PL:
  • Program (P3)
    \[
    \begin{array}{crcccccccccc}
    &\textrm{min} & & -b_k & - & a_{k1}x_1 & - & \ldots & - & a_{kn}x_n & + & x_{n+k}\\
    i\ne k \Rightarrow & x_{n+i}& = & b_i - b_k & + & (a_{i1}-a_{k1})x_1 & + & \ldots & + & (a_{in}-a_{kn})x_n & + & x_{n+k}\\
    & x_{0}& = & -b_k & - & a_{k1}x_1 & - & \ldots & - & a_{kn}x_n & + & x_{n+k}\\
    & x_i &\ge &0
    \end{array}
    \]

  • Zauważmy, że program (P3) jest równoważny programowi (P2) oraz spełnia niezmiennik 1. Za pomocą algorytmu simplex znajdujemy rozwiązanie optymalne \(\mathbf{x}^*\). (Zauważmy, że program (P2) jest ograniczony: wartością funkcji celu jest wartość \(x_0\), która jest nieujemna.) W używanym algorytmie simplex dokonujemy jednak małej modyfikacji w regule wyboru zmiennej wychodzącej: jeśli \(x_0\) może opuścić bazę, to ją opuszcza.

  • Jeśli otrzymaliśmy \(x^*_0>0\) zwracamy informację ,,PROGRAM SPRZECZNY''. Istotnie, gdyby istniało rozwiązanie dopuszczalne \(\mathbf{x}\) programu (P1), to \((0,\mathbf{x})\) byłoby rozwiązaniem dopuszczalnym programu (P2) o wartości funkcji celu 0.
  • Jeśli otrzymaliśmy \(x^*_0=0\) oraz \(x_0\) jest niebazowa, wystarczy z ostatniego PL wygenerowanego przez algorytm simplex usunąć zmienne \(x_0\). Otrzymujemy wtedy program równoważny programowi (P1), który spełnia niezmiennik 1.
  • Pozostaje przypadek, gdy otrzymaliśmy \(x^*_0=0\) oraz \(x_0\) jest bazowa. Pokażemy, że jest to niemożliwe. Rozważmy ostatnią operację Pivot. Powiedzmy, że zmienną wchodzącą było \(x_j\), a wychodzącą \(x_i\), dla pewnego \(i\ne j\). Przed wykonaniem operacji Pivot zarówno \(x_i\) jak i \(x_0\) były bazowe, a więc na podstawie niezmiennika 1 odpowiadały im dwa równania:
    \[
    \begin{array}{rccccccc}
    x_{0}& = & b_0 & + & \ldots & + & a_{0j}x_j\\
    x_{i}& = & b_i & + & \ldots & + & a_{ij}x_j.
    \end{array}
    \]
    Po wykonaniu operacji Pivot, równanie zawierające \(x_0\) ma postać
    \[
    x_{0} = b_0 + a_{0j}\cdot \frac{b_i}{-a_{ij}} + \sum_{j\in N} a'_{0j}x_{N_j}.
    \]
    Wiemy jednak, że po tej operacji wyraz wolny w tym równaniu jest równy 0, a więc \(\frac{b_i}{-a_{ij}} = \frac{b_0}{-a_{0j}}\). Ponieważ \(x_i\) została wybrana jako zmienna wychodząca, więc \(\frac{b_i}{-a_{ij}}=\min\{\frac{b_{\ell}}{-a_{{\ell}j}}\ |\ a_{{\ell}j} < 0\}\). Wówczas także \(\frac{b_0}{-a_{0j}}=\min\{\frac{b_{\ell}}{-a_{{\ell}j}}\ |\ a_{{\ell}j} < 0\}\), czyli zmienna \(x_0\) również była kandydatem do opuszczenia bazy, a więc zgodnie z naszą regułą, musiała ją opuścić i cała rozważana sytuacja nie mogła mieć miejsca.
  • Programowanie liniowe 3: Dualność

    Motywacja: certyfikat optymalności

    Dla niektórych problemów znane są algorytmy, które wraz z rozwiązaniem generują tzw. certyfikat poprawności, czyli dodatkową informację z pomocą której możemy łatwo (tzn. algorytmem, który jest nie tylko wielomianowy, ale również istotnie prostszy od algorytmu generującego rozwiązanie) sprawdzić, czy
    zwrócone rozwiązanie jest poprawne (lub optymalne w przypadku problemów optymalizacyjnych). Algorytmy te określane są mianem algorytmów certyfikujących. Oto kilka przykładów:

    • Algorytm sprawdzający, czy dany graf jest dwudzielny może zwracać 2-kolorowanie takiego grafu lub cykl nieparzysty.
    • Algorytm znajdowania maksymalnego przepływu wraz z przepływem może zwracać minimalny przekrój.
    • Istnieje algorytm testujący planarność grafów, który zwraca rysunek na płaszczyźnie bez przecięć krawędzi lub podgraf homeomorficzny z \(K_{3,3}\) lub \(K_5\).

    Algorytmy certyfikujące oprócz znaczenia teoretycznego (aby generować takie certyfikaty należy dobrze zrozumieć problem) mają dużą wartość praktyczną: pomyślmy choćby o testowaniu poprawności kodu.

    Czy dla programowania liniowego istnieją certyfikaty poprawności? Rozważmy decyzyjną wersję problemu programowania liniowego: mając dany (minimalizacyjny) PL i liczbę \(\delta\) rozstrzygnąć, czy istnieje dopuszczalny \(\mathbf{x}\) taki, że \(\mathbf{c}^T\mathbf{x} \le \delta\). Załóżmy dla uproszczenia, że nasz program jest ograniczony. Wraz z odpowiedzią TAK algorytm certyfikujący może zwrócić współrzędne \(\mathbf{x}\) (można nawet pokazać, że te współrzędne można reprezentować w pamięci wielomianowej względem rozmiaru danych). A więc istnieje certyfikat pozytywny. Z punktu widzenia teorii złożoności, dowodzi to, że problem jest w klasie NP. W dodatku algorytm weryfikacji certyfikatu jest banalny: po prostu sprawdzamy odpowiednie nierówności. Czy możemy podać certyfikat negatywny, tzn. certyfikat poprawności dla odpowiedzi NIE? Gdyby ten certyfikat był również krótki, mielibyśmy dowód, że programowanie liniowe jest w klasie co-NP.

    W tym rozdziale przedstawimy pojęcie dualności programowania liniowego, które dostarcza odpowiedzi na powyższe pytania. Jak zobaczymy w kolejnych wykładach, dualność programowania liniowego ma dużo więcej konsekwencji w matematyce i informatyce.

    Poszukiwanie ograniczenia na wartość rozwiązania optymalnego

    Rozważmy następujący PL w postaci standardowej:

    Program (P1)
    \[
    \begin{array}{ll}
    \textrm{zmaksymalizuj} & 3x_1 - x_2 + 2x_3 \\
    \textrm{z zachowaniem warunków} & x_1 - x_2 + \frac{1}{2}x_3 \le 4 \\
    & 4x_1 + 2x_2 + 3x_3 \le 20\\
    & x_1,x_2,x_3\ge 0
    \end{array}
    \]

    Łatwo sprawdzić, że punkt \((x_1=2,x_2=0,x_3=4)\) jest rozwiązaniem dopuszczalnym o wartości funkcji celu \(14\).

    Zauważmy, że skoro \(x_1,x_2,x_3\ge 0\), to \(3x_1 \le 4x_1\), a także \(-x_2 \le 2x_2\) oraz \(2x_3 \le 3x_3\). Stąd, \(3x_1 - x_2 + 2x_3 \le 4x_1 + 2x_2 + 3x_3 \le 20\). Dostaliśmy górne ograniczenie! W tym przypadku możemy zauważyć nawet więcej. Ponieważ \(3 \le 1+\frac{1}{2}\cdot 4\), \(-1 \le -1+\frac{1}{2}\cdot 2\) oraz \(2 \le \frac{1}{2}+\frac{1}{2}\cdot 3\), więc
    \[
    \begin{split}
    \nonumber
    3x_1 - x_2 + 2x_3 \le&\; x_1 - x_2 + \frac{1}{2}x_3 \; + \\
    &\; \frac{1}{2}\cdot (4x_1 + 2x_2 + 3x_3) \le 4+\frac{1}{2}\cdot 20 = 14.
    \end{split}
    \]

    Udowodniliśmy (choć, trzeba przyznać, dość fartownie), że wartość funkcji celu nigdy nie przekracza \(14\), a więc mamy certyfikat optymalności, w dodatku bardzo krotki (kilka nierówności) i prosty do sprawdzenia.
    Możemy to rozumowanie uogólnić: można brać dowolne kombinacje liniowe nierówności t.ż.:

    • współczynniki kombinacji liniowej są nieujemne (bo inaczej odwrócą się kierunki nierówności),
    • dla dowolnego \(i\), współczynnik funkcji celu przy \(x_i\) jest ograniczony z góry przez odpowiednią kombinację liniową współczynników przy \(x_i\) w nierównościach.

    Można to zapisać za pomocą programu liniowego:

    Program (D1)
    \[
    \begin{array}{ll}
    \textrm{zminimalizuj} & 4y_1 + 20y_2 \\
    \textrm{z zachowaniem warunków} & y_1 + 4y_2 \ge 3 \\
    & -y_1 + 2y_2 \ge -1\\
    & \frac{1}{2}y_1+3y_2\ge 2\\
    & y_1, y_2 \ge 0.
    \end{array}
    \]

    Powyższy program będziemy nazywać programem dualnym do programu (P1). Ogólnie, dla programu (będziemy go nazywać programem prymalnym)

    Program (P2)
    \[
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & \sum_{j=1}^n c_jx_j & \\
    \textrm{z zachowaniem warunków} & \sum_{j=1}^n a_{ij}x_j \le b_i& i=1,\ldots,m\\
    & x_j \ge 0&j=1,\ldots,n
    \end{array}
    \]

    program dualny ma postać:

    Program (D2)
    \[
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zminimalizuj} & \sum_{i=1}^m b_iy_i & \\
    \textrm{z zachowaniem warunków} & \sum_{i=1}^m a_{ij}y_i \ge c_j& j=1,\ldots,n\\
    & y_i \ge 0&i=1,\ldots,m.
    \end{array}
    \]

    Zauważmy, że macierz współczynników ograniczeń programu (D2) jest transpozycją macierzy dla programu (P2) (porównaj też dla programów (P1) i (D1)). Daje to niezwykle prosty, mechaniczny sposób konstruowania programu dualnego. Mianowicie dla programu

    \[
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & \mathbf{c}^T\mathbf{x} & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \le \mathbf{b}&\\
    & \mathbf{x} \ge \mathbf{0}&
    \end{array}
    \]

    program dualny ma postać:

    \[
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zminimalizuj} & \mathbf{b}^T\mathbf{y} & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}^T\mathbf{y} \ge \mathbf{c} \\
    & \mathbf{y}\ge\mathbf{0}
    \end{array}
    \]

    Dualność jest relacją symetryczną, tzn. mówimy także, że (P2) jest dualny do (D2). Innymi słowy, program dualny do programu dualnego to program prymalny.

    Słaba dualność i komplementarne warunki swobody

    Pozostańmy przy programach w postaci standardowej. Z konstrukcji programu dualnego wynika następujący fakt (dla porządku podamy jednak dowód).

    Twierdzenie 1 [o słabej dualności]
    Niech \(\mathbf{x}\) i \(\mathbf{y}\) będą dowolnymi rozwiązaniami dopuszczalnymi odpowiednio programów (P2) i (D2). Wówczas \(\mathbf{c}^T \mathbf{x} \le \mathbf{b}^T \mathbf{y}\).

    Dowód
    Ponieważ dla każdego \(j=1,\ldots,n\), mamy \(x_j\ge 0\) oraz \(\sum_{i=1}^m a_{ij}y_i \ge c_j\), więc
    \[(*)\quad\quad \quad\quad \quad\quad c_j x_j \le \left(\sum_{i=1}^m a_{ij}y_i\right) x_j \quad\quad \forall j=1,\ldots,n.\]
    Podobnie, ponieważ dla każdego \(i=1,\ldots,m\), mamy \(y_i\ge 0\) oraz \(\sum_{j=1}^n a_{ij}x_j \le b_i\), więc
    \[(**)\quad\quad \quad\quad \quad\quad\left(\sum_{j=1}^n a_{ij}x_j\right) y_i \le b_i y_i \quad\quad \forall i=1,\ldots,m.\]
    Stąd,
    \[(***)\quad\quad \quad\quad \quad\quad\mathbf{c}^T\mathbf{x}=\sum_{j=1}^n c_jx_j\le \sum_{j=1}^n\left(\sum_{i=1}^m a_{ij}y_i\right) x_j=\sum_{i=1}^m\left(\sum_{j=1}^n a_{ij}x_j\right) y_i\le\sum_{i=1}^m b_i y_i = \mathbf{b}^T\mathbf{y}.
    \] ♦

    Zastanówmy się, kiedy rozwiązania optymalne programu prymalnego (P2) i dualnego (D2) spotykają się, czyli \(\mathbf{c}^T\mathbf{x}=\mathbf{b}^T\mathbf{y}\). Z dowodu twierdzenia o słabej dualności widzimy, że jest tak wtedy i tylko wtedy gdy obie nierówności w (***) są równościami. Tak może się wydarzyć tylko wtedy, gdy gdy wszystkie nierówności w (*) i (**) są równościami. To dowodzi następującego twierdzenia:

    Twierdzenie (o komplementarnych warunkach swobody)
    Niech \(\mathbf{x}\) i \(\mathbf{y}\) będą rozwiązaniami dopuszczalnymi odpowiednio dla zadania prymalnego i dualnego w postaci standardowej. Rozwiązania \(\mathbf{x}\) i \(\mathbf{y}\) są oba optymalne wtedy i tylko wtedy gdy

    • prymalne komplementarne warunki swobody: dla każdego \(j=1,\ldots,n\) albo \(x_j=0\) albo \(\sum_{i=1}^m a_{ij}y_i=c_j\).
      (tzn. albo \(x_j=0\) albo \(j\)-ta nierówność programu dualnego jest spełniona z równością.)
    • dualne komplementarne warunki swobody dla każdego \(i=1,\ldots,m\) albo \(y_i=0\) albo \(\sum_{j=1}^n a_{ij}x_j=b_i\).
      (tzn. albo \(y_i=0\) albo \(i\)-ta nierówność programu prymalnego jest spełniona z równością.)

    Programy dualne do ogólnych programów liniowych

    Nawet gdy program nie jest w postaci standardowej możemy napisać program dualny, kierując się tą samą zasadą: poszukujemy jak najlepszego górnego ograniczenia na wartość funkcji celu. Zobaczmy np. co się dzieje, gdy program zawiera równość:

    \[
    \label{eq:primal11}
    \begin{array}{ll}
    \textrm{zmaksymalizuj} & 3x_1 - x_2 + 2x_3 \\
    \textrm{z zachowaniem warunków} & x_1 - x_2 + \frac{1}{2}x_3 \le 4 \\
    & - 4x_1 - 2x_2 - 3x_3 = -20\\
    & x_1,x_2,x_3\ge 0
    \end{array}
    \]

    Podobnie jak poprzednio, dodając pierwszy warunek pomnożony przez \(y_1 = 1\) do drugiego warunku pomnożonego przez \(y_2=-\frac{1}{2}\) dostajemy ograniczenie górne równe \(14\). Zauważmy, że w kombinacji liniowej warunków, współczynniki dla nierówności muszą być nieujemne, natomiast dla równości mogą być dowolne (w tym przypadku wybraliśmy ujemny). Program dualny wygląda następująco:

    \[
    \label{eq:dual11}
    \begin{array}{ll}
    \textrm{zminimalizuj} & 4y_1 + 20y_2 \\
    \textrm{z zachowaniem warunków} & y_1 + 4y_2 \ge 3 \\
    & -y_1 + 2y_2 \ge -1\\
    & \frac{1}{2}y_1+3y_2\ge 2\\
    & y_1\ge 0.
    \end{array}
    \]

    A co by się stało, gdyby w programie prymalnym dodatkowo mogły się pojawiać zmienne bez warunku nieujemności? Rozważmy np.

    \[
    \label{eq:primal112}
    \begin{array}{ll}
    \textrm{zmaksymalizuj} & 3x_1 - x_2 + 2x_3 \\
    \textrm{z zachowaniem warunków} & x_1 - x_2 + \frac{1}{2}x_3 \le 4 \\
    & 4x_1 + 2x_2 + 3x_3 = 20\\
    & x_1,x_3\ge 0
    \end{array}
    \]
    Wówczas nie możemy już napisać, że \(3x_1 - x_2 + 2x_3 \le 4x_1 + 2x_2 + 3x_3\), gdyż niekoniecznie \(-x_2 \le 2x_2\). Jeśli jednak pomnożymy pierwszą nierówność przez 3 i dodamy do drugiej, dostaniemy:

    \[
    \begin{split}
    \nonumber
    3x_1 - x_2 + 2x_3 \le&\; 3 (x_1 - x_2 + \frac{1}{2}x_3) \; + \\
    &\; 4x_1 + 2x_2 + 3x_3 \le 12+20 = 32,
    \end{split}
    \]
    gdyż \(3x_1 \le 7x_1\), \(-x_2 = -x_2\) oraz \(2x_3\le 3x_3\). Znalezienie najlepszej kombinacji liniowej warunków odpowiada programowi:

    \[
    \label{eq:dual111}
    \begin{array}{ll}
    \textrm{zminimalizuj} & 4y_1 + 20y_2 \\
    \textrm{z zachowaniem warunków} & y_1 + 4y_2 \ge 3 \\
    & -y_1 + 2y_2 = -1\\
    & \frac{1}{2}y_1+3y_2\ge 2\\
    & y_1\ge 0.
    \end{array}
    \]

    Ogólnie, program dualny konstruujemy zgodnie z poniższą zasadą:

    Tworzenie programów dualnych

    Można łatwo sprawdzić, że program dualny zbudowany zgodnie z powyższymi wytycznymi również spełnia twierdzenie o słabej dualności (a także twierdzenie o silnej dualności, które udowodnimy w kolejnym punkcie).

    Silna dualność

    Twierdzenie o słabej dualności można również wyrazić w następujący sposób:

    Wniosek
    Jeśli \(z\) jest wartością funkcji celu rozwiązania optymalnego prymalnego minimalizacyjnego PL, natomiast \(w\) jest wartością funkcji celu rozwiązania optymalnego programu dualnego, to \(z \ge w\).

    Wynika stąd w szczególności, że gdy \(z=w\), to rozwiązanie optymalne programu dualnego jest certyfikatem optymalności naszego rozwiązania programu prymalnego. Okazuje się, że tak jest zawsze!

    Twierdzenie (o silnej dualności)
    Jeśli \(z\) jest wartością funkcji celu rozwiązania optymalnego prymalnego PL, natomiast \(w\) jest wartością funkcji celu rozwiązania optymalnego programu dualnego, to \(z = w\).

    Dowód
    Dla uproszczenia przeprowadzimy dowód dla przypadku programu w maksymalizacyjnej postaci standardowej (dowód dla ogólnej postaci jest analogiczny). Oto program prymalny:

    Program (P3)
    \[
    \label{eq:st-primal-proof}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & \mathbf{c}^T\mathbf{x} & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \le \mathbf{b}&\\
    & \mathbf{x} \ge \mathbf{0},&
    \end{array}
    \]

    oraz program dualny:

    Program (D3)
    \[
    \label{eq:st-dual-proof}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zminimalizuj} & \mathbf{b}^T\mathbf{y} & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}^T\mathbf{y} \ge \mathbf{c} \\
    & \mathbf{y}\ge\mathbf{0}.
    \end{array}
    \]

    Niech \(\mathbf{x}^*\) będzie rozwiązaniem optymalnym programu prymalnego (P3). Ze słabej dualności, wystarczy pokazać rozwiązanie dopuszczalne \(\mathbf{y}\) programu dualnego (D3) t.ż. \(\mathbf{c}^T \mathbf{x}^* = \mathbf{b}^T \mathbf{y}\). Uruchamiamy algorytm simplex (patrz poprzedni wykład) na programie (P3).
    W pierwszej iteracji algorytm simplex buduje program

    Program (P3-start)
    \[
    \label{eq:P_0}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & z & \\
    \textrm{z zachowaniem warunków} & z = \sum_{j=1}^n c_jx_j & \\
    & x_{n+i}=b_i - \sum_{j=1}^n a_{ij}x_j & i=1,\ldots,m\\
    & x_j \ge 0&j=1,\ldots,n+m.
    \end{array}
    \]

    Rozważmy program z ostatniej iteracji algorytmu simplex.

    Program (P3-stop)
    \[
    \label{eq:P'}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & z & \\
    \textrm{z zachowaniem warunków} & z = v + \sum_{j\in N} c'_jx_j & \\
    & x_i=b'_i-\sum_{j\in N} a'_{ij}x_j& i\in B\\
    & x_j \ge 0&j=1,\ldots,n+m.
    \end{array}
    \]

    Ponieważ program prymalny jest ograniczony, więc dla każdego \(j\in N\), \(c'_j \le 0\) oraz algorytm simplex zwraca rozwiązanie optymalne \(\mathbf{x}^*\) takie, że
    \[
    x^*_i = \left\{ \begin{array}{cl}
    0 & \text{gdy \(i\in N\)} \\
    b'_i & \text{gdy \(i\in B\).}
    \end{array} \right.
    \]

    Określimy teraz pewne rozwiązanie programu dualnego (D3):
    \[
    y_i := \left\{ \begin{array}{cl}
    -c'_{n+i} & \text{gdy \(n+i\in N\)} \\
    0 & \text{w p. p.}
    \end{array} \right.
    \]

    Pokażemy teraz, że \(\mathbf{y}\) jest dopuszczalny o wartości funkcji celu równej \(\mathbf{c}^T\mathbf{x}^*\). Określamy \(c'_j:=0\) dla \(j\in B\). Wówczas pierwszy warunek programu (P3-stop) możemy zapisać jako \(z = v + \sum_{j=1}^{n+m} c'_jx_j\).

    Weźmy dowolne \((x_1,\ldots,x_n)\in \mathbb{R}^n\). Określmy \(x_{n+i} = b_i - \sum_{i=1}^n a_{ij}x_j\) oraz \(z=\sum_{i=1}^n a_{ij}x_j\). Wówczas \((z,x_1,\ldots,x_{n+m})\) jest rozwiązaniem dopuszczalnym programu (P3-start). Ponieważ program (P3-stop) jest równoważny (P3-start), tzn. powstaje z (P3-start) na drodze ciągu operacji elementarnych, więc \((z,x_1,\ldots,x_{n+m})\) jest również rozwiązaniem dopuszczalnym programu (P3-stop). Z faktu, iż w obu programach spełnione są warunki zawierające zmienną \(z\) mamy, iż:
    \[
    \begin{array}{rcl}
    \sum_{j=1}^n c_jx_j & = & v + \sum_{j=1}^{n+m} c'_jx_j \\
    & = & v + \sum_{j=1}^{n} c'_jx_j + \sum_{i=1}^{m} c'_{n+i}x_{n+i} \\
    & = & v + \sum_{j=1}^{n} c'_jx_j + \sum_{i=1}^{m} (-y_i)(b_i - \sum_{j=1}^n a_{ij}x_j).
    \end{array}
    \]
    Po przegrupowaniu dostajemy, że dla dowolnego \((x_1,\ldots,x_n)\in \mathbb{R}^n\),
    \[
    \sum_{j=1}^n c_jx_j = \left(v - \sum_{i=1}^{m} y_i b_i\right) + \sum_{j=1}^n \left(c'_j + \sum_{i=1}^m y_i a_{ij}\right) x_j.
    \]

    Podstawiamy \((x_1,\ldots,x_n) = (0,\ldots,0)\) i mamy \(v = \sum_{i=1}^{m} y_i b_i\). Ponieważ \(\mathbf{c}^T\mathbf{x}^* = v\), więc \(\mathbf{c}^T\mathbf{x}^* = \mathbf{b}^T \mathbf{y}\). Pozostaje pokazać dopuszczalność \(\mathbf{y}\). W tym celu rozważmy dowolne \(k\in\{1,\ldots, n\}\). Podstawiamy \(x_j=[j=k]\) dla każdego \(j=1,\ldots,n\). Otrzymujemy
    \[
    c_k = \underbrace{(v - \sum_{i=1}^{m} y_i b_i)}_{=0} + \underbrace{\quad c'_k\quad}_{\le 0} + \sum_{i=1}^m y_i a_{ik},
    \]
    co implikuje \(\sum_{i=1}^m y_i a_{ik} \ge c_k\) dla każdego \(k\), czyli \(\mathbf{A}^T \mathbf{y} \ge \mathbf{c}\). Zauważmy, że ponieważ \(c'_j\le 0\) dla każdego \(j\), więc \(\mathbf{y} \ge 0\). Pokazaliśmy zatem, że \(\mathbf{y}\) jest rozwiązaniem dopuszczalnym programu (D3) o wartości funkcji celu \(\mathbf{c}^T\mathbf{x}^*\), co kończy dowód. ♦

    Z powyższego dowodu wynika, że algorytm simplex znajduje równocześnie rozwiązania optymalne programu prymalnego i dualnego. W niektórych sytuacjach możemy uzyskać skrócenie czasu działania algorytmu stosując tzw. dualny algorytm simplex, tzn. uruchamiać algorytm simplex dla programu dualnego.

    Programowanie liniowe 4: Programy całkowitoliczbowe i kilka zastosowań dualności

    Programowanie całkowitoliczbowe

    Program (liniowy) całkowitoliczbowy to program liniowy z dodatkowym wymaganiem, aby wartości wszystkich zmiennych były całkowitoliczbowe. Ten z pozoru niewinny warunek całkowicie zmienia złożoność problemu: większość naturalnych problemów NP-trudnych można bardzo łatwo wyrazić jako liniowe programy całkowitoliczbowe, np. problem pokrycia wierzchołkowego w grafie \(G=(V,E)\) jest równoważny programowi:

    \[
    \label{eq:vc}
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} x_v \\
    & x_u+x_v \ge 1 & \forall uv \in E\\
    & x_v \in \{0,1\} & \forall v \in V,\\
    \end{array}
    \]
    ponieważ ostatni warunek możemy zastąpić przez koniunkcję \(0 \le x_v \le 1\) i \(x_v \in \mathbb{Z}\). Jeśli min zastąpimy przez max, a \(\ge\) przez \(\le\) otrzymamy z kolei problem najmniejszego zbioru niezależnego.

    Wniosek
    Problem programowania liniowego całkowitoliczbowego jest NP-trudny.

    Dokładne relaksacje i unimodularność

    Dla danego programu całkowitoliczbowego (I) jego relaksacją będziemy nazywać program liniowy (L), który powstaje po usunięciu warunku całkowitoliczbowości. Relaksacja jest dokładna, gdy wszystkie wierzchołki (L) są całkowitoliczbowe (tzn. mają całkowite współrzędne). Zauważmy, że jeśli relaksacja faktycznie jest dokładna, to w szczególności możemy rozwiązać program całkowitoliczbowy (I) w czasie wielomianowym. W tym celu wystarczy:

    1. rozwiązać (L) w czasie wielomianowym (np. metodą elipsoidalną) otrzymując rozwiązanie optymalne \(\mathbf{x}^*\),
    2. znaleźć wierzchołek \(\mathbf{x}'\) o wartości funkcji celu takiej samej jak dla \(\mathbf{x}^*\) (wielomianowo, np. tak jak w dowodzie twierdzenia th:opt->vtx z wykładu o geometrii programów liniowych)
    3. zwrócić \(\mathbf{x}'\) jako rozwiązanie programu całkowitoliczbowego (I).

    Poszukiwanie programów całkowitoliczbowych, których relaksacje są dokładne to użyteczne narzędzie w rozstrzyganiu, czy dany problem algorytmiczny należy do klasy \(\mathbf{P}\). W dowodzeniu, że dana relaksacja jest dokładna często używamy pojęcia całkowitej unimodularności.

    Mówimy, że macierz \(\mathbf{A}\) jest całkowicie unimodularna jeśli dla każdej podmacierzy \(\mathbf{A}'\) macierzy \(\mathbf{A}\), mamy \(\det \mathbf{A}'\in \{-1,0,1\}\).

    Twierdzenie 1
    Jeśli wektor \(\mathbf{b}\) jest całkowitoliczbowy i macierz \(\mathbf{A}\) jest całkowicie unimodularna to program \(\mathbf{A} \mathbf{x}\le \mathbf{b}\) ma wierzchołki całkowitoliczbowe (czyli jest dokładną relaksacją programu całkowitoliczbowego \(\mathbf{A} \mathbf{x}\le \mathbf{b}\)).

    Dowód powyższego twierdzenia pozostawiamy Czytelnikowi jako ćwiczenie. Wskazówka: skorzystaj z tego, że wierzchołki są bazowymi rozwiązaniami dopuszczalnymi oraz użyj wzorów Cramera.

    Maksymalny przepływ o minimalnym koszcie

    Rozważmy problem maksymalnego przepływu o minimalnym koszcie (patrz odpowiedni wykład). Poznaliśmy dwa algorytmy dla tego problemu (algorytm usuwania ujemnych cykli i algorytm najkrótszej ścieżki). Choć algorytm najkrótszej ścieżki jest bardzo efektywny w zastosowaniach, w których maksymalny przepływ ma niewielką wartość, w ogólności żaden z tych dwóch algorytmów nie jest wielomianowy (tzn. nie zależy wielomianowo od rozmiaru danych). Z pomocą programowania liniowego łatwo pokazać, że algorytm wielomianowy dla tego problemu istnieje, i to nawet w ogólnym przypadku (algorytm najkrótszej ścieżki działa tylko gdy w grafie wejściowym nie ma cykli o ujemnym koszcie). Wystarczy jednym z algorytmów wielomianowych rozwiązać następujący program liniowy:

    Program (F)
    \[
    \begin{array}{ll@{\hspace{7mm}}l}
    \textrm{zminimalizuj} & \sum_{(u,v)\in E} a(u,v) \cdot f_{uv} & \\
    \textrm{z zachowaniem warunków} & \sum_{(u,v)\in E} f_{uv} - \sum_{(v,u)\in E}f_{vu} = 0 & \forall v \in V\setminus\{s,t\} \\
    & f_{vw} \le c(v,w) & \forall (v,w)\in E \\
    & f_{vw} \ge 0 & \forall (v,w)\in E \\
    & \sum_{(s,v)\in E}f_{sv}=f^*,
    \end{array}
    \]

    gdzie \(f^*\) jest wartością maksymalnego przepływu (którą możemy wyliczyć w czasie wielomianowym algorytmem Edmondsa-Karpa lub rozwiązując program liniowy z przykładu 2 z pierwszego wykładu o programowaniu liniowym).

    Znane nam algorytmy dla problemów przepływowych mają tę użyteczną cechę, że gdy przepustowości w sieci są całkowitoliczbowe, to również przepływ znajdowany przez te algorytmy jest całkowitoliczbowy. Okazuje się, że można to zagwarantować również rozwiązując powyższy program, gdyż jego macierz jest całkowicie unimodularna. Poniżej przestawiamy dowód tego faktu.

    Twierdzenie 2
    Program (F) ma wierzchołki całkowitoliczbowe, o ile przepustowości krawędzi są liczbami całkowitymi.

    Dowód
    Pokażemy, że macierz programu (F) jest unimodularna. Rozważamy dowolną podmacierz \(A'\) i chcemy wykazać, że \(\det A'\in \{-1,0,1\}\). Dowodzimy przez indukcję ze względu na liczbę elementów macierzy \(A'\), że \(\det A' \in \{-1,0,1\}\). Dla macierzy o wymiarach \(1\times 1\) mamy \(\det A' \in \{-1,0,1\}\). Rozważmy teraz podmacierz \(A'\) o dowolnych wymiarach. Jeśli \(A'\) ma kolumnę, która zawiera co najwyżej jedną 1-kę, kończymy dowód korzystając z rozwinięcia Laplace'a względem tej kolumny i założenia indukcyjnego. Postępujemy analogicznie dla wierszy. Został nam przypadek, gdy każdy wiersz i każda kolumna zawiera co najmniej dwie 1-ki. Stąd, każdy wiersz \(A'\) pochodzi od ograniczenia typu \(\sum_{(u,v)\in E} f_{uv} - \sum_{(v,u)\in E}f_{vu} = 0\) lub od ograniczenia \(\sum_{(s,v)\in E}f_{sv}=f^*\). Ale dla każdej zmiennej \(f_{ab}\) są tylko dwa takie ograniczenia: w jednym (dla wierzchołka \(a\)) \(f_{ab}\) pojawia się ze współczynnikiem \(+1\), w drugim (dla wierzchołka \(b\)) ze współczynnikiem \(-1\). Po dodaniu wszystkich wierszy \(A'\) dostaniemy więc wektor zerowy, a więc wiersze \(A'\) są liniowo zależne i \(\det A'=0\). ♦

    Skojarzenia w grafach dwudzielnych i twierdzenie Koniga-Egervary'ego

    Rozważmy problem maksymalnego skojarzenia w danym grafie \(G=(V,E)\). Problem ten możemy łatwo sformułować jako zadanie programowania liniowego całkowitoliczbowego.

    Program (IM1)
    \[
    \label{eq:matching}
    \begin{array}{rll}
    \textrm{max} & \sum_{e \in E} x_e \\
    & \sum_{vw\in E}x_{vw} \le 1 & \forall v \in V\\
    & x_e \in \{0,1\} & \forall e \in E.\\
    \end{array}
    \]

    Relaksacja tego programu wygląda następująco:

    Program (M1)
    \[
    \label{eq:matching-lp}
    \begin{array}{rll}
    \textrm{max} & \sum_{e \in E} x_e \\
    & \sum_{vw\in E}x_{vw} \le 1 & \forall v \in V\\
    & x_e \ge 0 & \forall e \in E.\\
    \end{array}
    \]

    Zauważmy, że w powyższym programie mogliśmy opuścić warunek \(x_e \le 1\), gdyż i tak jest on spełniony dla każdego rozwiązania dopuszczalnego programu (M1).

    Twierdzenie 3
    Jeśli \(G\) jest dwudzielny, to macierz programu (M1) jest unimodularna.

    Dowód
    Ponieważ \(G\) jest dwudzielny, więc jego zbiór wierzchołków dzieli się na dwa rozłączne zbiory \(V=X\cup Y\) takie, że każda krawędź ma 1 koniec w \(X\) i 1 koniec w \(Y\). Niech \(A'\) będzie dowolną podmacierzą macierzy programu (M1). Stosujemy indukcję podobnie jak w dowodzie twierdzenia 2. Analogicznie jak poprzednio możemy założyć, że w każdym wierszu i każdej kolumnie \(A'\) są co najmniej dwie 1-ki. Stąd każdy wiersz \(A'\) pochodzi od wiersza typu \(\sum_{vw\in E}x_{vw} \le 1\) macierzy \(A\). Zauważamy, że każda kolumna macierzy \(A'\) zawiera dokładnie dwie jedynki: jedną w pewnym wierszu odpowiadającemu wierzchołkowi z \(X\), drugą w wierszu odpowiadającemu wierzchołkowi z \(Y\). Stąd, po dodaniu wszystkich wierszy \(A'\) odpowiadających wierzchołkom z \(X\) dostaniemy taki sam wektor jak po dodaniu wszystkich wierszy \(A'\) odpowiadających wierzchołkom z \(Y\). A więc wiersze macierzy \(A'\) są liniowo zależne, czyli \(\det A'=0\). ♦

    Jako wniosek dostajemy, że (M1) jest dokładną relaksacją (IM1). W konsekwencji mamy

    Wniosek
    Problem największego skojarzenia w grafach dwudzielnych jest w klasie \(\mathbf{P}\).

    Ten wniosek można oczywiście również otrzymać podając konkretny algorytm wielomianowy (patrz np. wykład o skojarzeniach w grafach dwudzielnych).

    Na marginesie dodajmy, że (M1) nie jest dokładną relaksacją (IM1) w klasie wszystkich grafów (łatwo to sprawdzić np. dla trójkąta). Aby otrzymać program, którego relaksacja jest dokładna, należy do (M1) dodać wykładniczą liczbę warunków: dla każdego nieparzystego zbioru wierzchołków \(S\subseteq V\) dodajemy warunek \(\sum_{e=vw: v,w\in S} x_e \le (|S|-1)/2\).

    Powróćmy do grafów dwudzielnych. Rozważmy program dualny do programu (M1):

    Program (VC)
    \[
    \label{eq:vc-2}
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} y_v \\
    & y_u+y_v \ge 1 & \forall uv \in E\\
    & y_v \ge 0 & \forall v \in V.\\
    \end{array}
    \]

    W powyższym programie moglibyśmy dodać warunek \(y_v \le 1\): i tak jest on spełniony dla każdego rozwiązania optymalnego programu (VC). Ponieważ macierz programu (VC) jest transpozycją macierzy programu (M1), więc również jest unimodularna. Stąd program (VC) ma rozwiązanie optymalne, które jest również rozwiązaniem optymalnym następującego programu całkowitoliczbowego

    Program (IVC)
    \[
    \label{eq:vc-3}
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} y_v \\
    & y_u+y_v \ge 1 & \forall uv \in E\\
    & y_v \in \{0,1\} & \forall v \in V.\\
    \end{array}
    \]

    Z kolei zauważamy, że powyższy program jest równoważny problemowi znalezienia najmniejszego pokrycia wierzchołkowego grafu \(G\). Na podstawie twierdzenia o silnej dualności otrzymujemy

    Twierdzenie [Kőnig, Egervary]
    W grafie dwudzielnym rozmiar największego skojarzenia jest równy liczności najmniejszego pokrycia wierzchołkowego. ♦

    Wiele innych klasycznych twierdzeń mini-maksowych okazuje się być szczególnymi przypadkami twierdzenia o dualności programów liniowych.

    Lemat Farkasa

    Pokażemy teraz zastosowanie dualności w algebrze liniowej. Przypomnijmy, że dla układów równań prawdziwa jest następująca elegancka własność:

    Lemat
    Są równoważne:

    1. istnieje \(\mathbf{x}\) taki, że \(\mathbf{A}\mathbf{x}=\mathbf{b}\),
    2. nie istnieje \(\mathbf{y}\) taki, że \(\mathbf{y}^T\mathbf{A}=\mathbf{0}\) oraz \(\mathbf{y}^T\mathbf{b}\ne\mathbf{0}\).

    Innymi słowy, albo układ ma rozwiązanie, albo istnieje taka kombinacja liniowa \(\mathbf{y}\) jego równań, która prowadzi do sprzeczności. Odpowiednik tego faktu dla programów liniowych nosi nazwę Lemat Farkasa.

    Lemat Farkasa
    Są równoważne:

    1. istnieje \(\mathbf{x}\) taki, że \(\mathbf{A}\mathbf{x}\le\mathbf{b}\),
    2. nie istnieje \(\mathbf{y}\ge\mathbf{0}\) taki, że \(\mathbf{A}^T\mathbf{y}=\mathbf{0}\) oraz \(\mathbf{y}^T\mathbf{b}<0\).

    Dowód
    Rozważmy (prymalny) program liniowy:
    \[
    \label{eq:st-primal-farkas}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zmaksymalizuj} & 0 & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}\mathbf{x} \le \mathbf{b}&\\
    & \mathbf{x} \text{ nieograniczony}&
    \end{array}
    \]
    Napiszmy program dualny zgodnie z zasadami z wykładu o dualności.
    \[
    \label{eq:st-dual-farkas}
    \begin{array}{ll@{\hspace{15mm}}l}
    \textrm{zminimalizuj} & \mathbf{b}^T \mathbf{y} & \\
    \textrm{z zachowaniem warunków} & \mathbf{A}^T\mathbf{y} = \mathbf{0}&\\
    & \mathbf{y} \ge 0.&
    \end{array}
    \]

    (i) \(\Rightarrow\) (ii). Jeśli istnieje \(\mathbf{x}\) taki, że \(\mathbf{A}\mathbf{x}\le\mathbf{b}\), to program prymalny jest dopuszczalny i ma rozwiązanie optymalne o koszcie 0. Z twierdzenia o (słabej) dualności, program dualny ma rozwiązań dopuszczalnych o wartości funkcji celu \(<0\).

    (ii) \(\Rightarrow\) (i). Jeśli nie istnieje \(\mathbf{y}\ge\mathbf{0}\) taki, że \(\mathbf{A}^T\mathbf{y}=\mathbf{0}\) oraz \(\mathbf{y}^T\mathbf{b}<0\), oznacza to, że \(\mathbf{y}=\mathbf{0}\) jest rozwiązaniem optymalnym programu dualnego. Z twierdzenia o (silnej) dualności program prymalny ma rozwiązanie optymalne, a więc układ nierówności \(\mathbf{A}\mathbf{x} \le \mathbf{b}\) jest niesprzeczny. ♦

    Algorytmy aproksymacyjne 1: algorytmy kombinatoryczne

    Wiele naturalnych problemów algorytmicznych to problemy optymalizacyjne, np:

    • maksymalny przepływ Dana jest sieć przepływowa. Znaleźć przepływ o maksymalnej wartości.
    • problem plecakowy Dane jest \(n\) przedmiotów o wartościach \(v_1,\ldots,v_n\) i rozmiarach \(s_1,\ldots,s_n\) oraz rozmiar plecaka \(B\). Które przedmioty należy włożyć do plecaka aby miały one sumarycznie największą możliwą wartość?
    • minimalne pokrycie wierzchołkowe Dany graf nieskierowany \(G=(V,E)\). Znaleźć najmniejsze pokrycie wierzchołkowe \(G\), tzn. podzbiór wierzchołków \(S\), który dotyka każdej krawędzi.
    • problem komiwojażera Dany pełny graf \(n\)-wierzchołkowy z nieujemnymi wagami na krawędziach. Znaleźć cykl Hamiltona o najmniejszej wadze (tzn. sumie wag krawędzi).

    W problemach tych dla danego egzemplarza problemu (sieć przepływowa, wartości i rozmiary przedmiotów wraz z wielkością plecaka, graf nieskierowany, tablica \({n \choose 2}\) liczb) jest określony pewien zbiór rozwiązań dopuszczalnych (przepływ, wybór przedmiotów mieszczących się w plecaku, pokrycie wierzchołkowe, cykl Hamiltona). Interesuje nas jednak nie dowolne rozwiązanie dopuszczalne ale rozwiązanie w jakimś sensie najlepsze. Dokładniej, na zbiorze rozwiązań dopuszczalnych mamy określoną pewną funkcję celu (o wartościach nieujemnych) i szukamy rozwiązania dopuszczalnego, dla którego wartość funkcji celu (wartość przepływu, sumaryczna wartość przedmiotów w plecaku, liczba wierzchołków w pokryciu, waga cyklu Hamiltona) jest optymalna (maksymalna dla dwóch pierwszych problemów, minimalna dla dwóch pozostałych). Wartość funkcji celu będziemy nazywać krótko kosztem rozwiązania (choć słowo to pasuje przede wszystkim do problemów minimalizacyjnych, w przypadku maksymalizacji lepszym słowem byłaby wartość).

    Z wcześniejszych wykładów wiemy, że problem maksymalnego przepływu można rozwiązać efektywnie, tj. w czasie wielomianowym. Jest to jednak sytuacja dość wyjątkowa: większość naturalnych problemów optymalizacyjnych jest NP-trudnych (dokładniej: NP-trudne są ich wersje decyzyjne). Jest tak np. w przypadku trzech ostatnich problemów z powyższej listy. Zamiast bezradnie rozkładać ręce, możemy jednak spytać co można w takiej sytuacji zrobić. Dotąd rozważaliśmy jedynie rozwiązania szybkie (wielomianowe), optymalne i działające dla dowolnych danych. Możemy pójść na kompromis i ograniczyć jedno (lub więcej) z tych wymagań. Współczesna algorytmika w przeważającej części zajmuje się badaniem efektów takich właśnie kompromisów. W tym wykładzie zajmiemy się prawdopodobnie najszerzej badaną wersją: rezygnujemy jedynie z optymalności rozwiązania. Mimo to, będziemy chcieli kontrolować jakość rozwiązań dopuszczalnych zwracanych przez nasze algorytmy. Będą nas interesowały wyniki typu "algorytm zawsze zwraca rozwiązanie gorsze od optymalnego o nie więcej niż 50%". Podamy teraz formalną definicję.

    Definicja
    Rozważmy wielomianowy algorytm, który dla każdego egzemplarza \(I\) pewnego problemu minimalizacyjnego zwraca rozwiązanie dopuszczalne o koszcie \({\rm ALG}(I)\). Niech \({\rm OPT}(I)\) oznacza koszt optymalnego rozwiązania dla egzemplarza \(I\). Powiemy, że taki algorytm jest \(\alpha\)-aproksymacyjny, gdy dla każdego egzemplarza \(I\),
    \[\frac{{\rm ALG}(I)}{{\rm OPT}(I)} \le \alpha.\]

    Dla problemów maksymalizacji definicja jest analogiczna, zmienia się jedynie znak nierówności:

    \[\frac{{\rm ALG}(I)}{{\rm OPT}(I)} \ge \alpha.\]

    Liczba \(\alpha\) jest nazywana współczynnikiem aproksymacji lub ilorazem aproksymacji. Zauważmy w szczególności, że w problemach minimalizacji \(\alpha \ge 1\), natomiast w problemach maksymalizacji \(\alpha \in [0,1]\). Czasem rozszerzamy powyższą definicję tak, że współczynnik aproksymacji \(\alpha\) nie jest stałą tylko pewną funkcją, z reguły zależną od rozmiaru danych: rozważane są np. algorytmy \(\log n
    \)-aproksymacyjne czy \(\sqrt{n}\)-aproksymacyjne.

    W dalszych rozważaniach będziemy pisać krótko \({\rm OPT}\) zamiast \({\rm OPT}(I)\), gdyż z reguły będzie jasne z kontekstu o jaki egzemplarz problemu chodzi.

    2-aproksymacyjny algorytm dla problemu minimalnego pokrycia wierzchołkowego

    Podamy teraz bardzo prosty algorytm aproksymacyjny, jednak nawet na tym elementarnym przykładzie widoczne będą pewne cechy obecne w bardziej zaawansowanych algorytmach. Przypomnijmy, że pokryciem wierzchołkowym nieskierowanego grafu \(G=(V,E)\) nazywamy dowolny zbiór wierzchołków \(S \subseteq V\) taki, że dla każdej krawędzi \(uv\in E\) zachodzi \(\{u,v\} \cap S \neq \emptyset\). Zajmiemy się następującym, klasycznym problemem.

    Problem pokrycia wierzchołkowego
    Dane: Graf nieskierowany \(G=(V,E)\).
    Problem: Znaleźć najliczniejsze pokrycie wierzchołkowe grafu \(G\).

    Nasz algorytm będzie następujący:

    Algorytm 2-aproksymacyjny
    1. Znajdź dowolne maksymalne ze względu na zawieranie skojarzenie \(M\) w grafie \(G\).
    2. Zwróć jako rozwiązanie zbiór wierzchołków skojarzonych \(V(M)\).

    Przykład: optymalne pokrycie i pokrycie wygenerowane przez algorytmPrzykład: optymalne pokrycie i pokrycie wygenerowane przez algorytm

    Zauważmy, że w punkcie 1. wymagane jest jedynie skojarzenie maksymalne ze względu na zawieranie, a nie najliczniejsze skojarzenie. Takie skojarzenie łatwo jest znaleźć algorytmem zachłannym w czasie liniowym (po prostu dopóki istnieje krawędź, której oba końce nie są skojarzone, dodajemy ją do skojarzenia). Cały algorytm działa więc w czasie liniowym.

    Twierdzenie 1 Powyższy algorytm jest poprawnym 2-aproksymacyjnym algorytmem dla problemu pokrycia wierzchołkowego

    Dowód
    Oznaczmy przez \(C\) zbiór wierzchołków zwracany przez algorytm. Najpierw pokażemy, że \(C\) jest pokryciem. Istotnie, gdyby istniała krawędź \(uv\) taka, że \(\{u,v\}\cap C = \emptyset\), oznaczałoby to, że ani \(u\) ani \(v\) nie jest skojarzony, a więc \(M \cup \{uv\}\) byłoby skojarzeniem zawierającym \(M\), a to jest niemożliwe.

    Teraz zajmiemy się współczynnikiem aproksymacji. Rozważmy dowolne pokrycie wierzchołkowe \(S\). Ponieważ każda krawędź skojarzenia musi mieć koniec w pokryciu, a z definicji skojarzenia te końce są parami różne więc \(|S| \ge |M|\). W szczególności jest to prawda dla pokrycia optymalnego, zatem:
    \[{\rm OPT} \ge |M|.\]
    Ponieważ \(|C|=2|M|\), więc \(|C| \le 2 {\rm OPT}\), co kończy dowód. ♦

    Zastanówmy się przez chwilę co tak naprawdę wydarzyło się w naszym algorytmie. Pojawia się tam trzech bohaterów: \(C\) -- czyli rozwiązanie zwrócone przez algorytm, \({\rm OPT}\) -- rozwiązanie optymalne (tu i w dalszej części wykładu używamy tego samego oznaczenia dla pewnego rozwiązania optymalnego i jego kosztu), oraz \(M\) -- skojarzenie maksymalne znalezione przez algorytm. Zastanówmy się jaka jest rola tego ostatniego bohatera, który pojawił się w naszej historii dość niespodziewanie. Skojarzenie \(M\) ma dwie kluczowe cechy: możemy je znaleźć w czasie wielomianowym oraz daje oszacowanie dolne na \({\rm OPT}\): w tym przypadku oszacowanie \({\rm OPT} \ge |M|\). Otrzymujemy więc obiekt, który w sensie naszej miary kosztu rozwiązania (mocy zbioru) jest lepszy niż \({\rm OPT}\) , ale nie jest rozwiązaniem dopuszczalnym. W punkcie 2. algorytmu przekształcamy ten obiekt w rozwiązanie dopuszczalne, tracąc przy tym (dwukrotnie) na koszcie. Skoro nasze rozwiązanie \(C\) jest dwukrotnie gorsze niż \(M\), a \(M\) było lepsze niż \({\rm OPT}\), to \(C\) jest co najwyżej 2 razy gorsze niż \({\rm OPT}\).

    Skojarzenie \(M\) jest tu swego rodzaju pośrednikiem między rozwiązaniem \(C\) a \({\rm OPT}\): umiemy porównać \(C\) z \(M\) oraz \(M\) z \({\rm OPT}\), razem dostajemy oszacowanie na \(C\) względem \({\rm OPT}\). Często projektując algorytm aproksymacyjny zaczynamy od wskazania takiego właśnie pośrednika, a następnie zastanawiamy się, jak z jego pomocą zbudować rozwiązanie dopuszczalne, tracąc możliwie mało na koszcie. Ten schemat pojawi się w kilku kolejnych przykładach.

    Problem komiwojażera i trudność aproksymacji

    W poprzednim przykładzie stosunkowo łatwo dostaliśmy algorytm aproksymacyjny ze współczynnikiem aproksymacji ograniczonym przez niewielką stałą (2). Pojawia się naturalne pytanie: czy jest to możliwe dla każdego problemu optymalizacyjnego (powiedzmy, takiego, którego wersja decyzyjna jest w klasie NP)? Oczywiście nie pokażemy tu twierdzenia w rodzaju "dla problemu X nie istnieje algorytm 10-aproksymacyjny", gdyż implikowałoby to, że \({\rm P}\ne {\rm NP}\), a jak wiemy jest to słynny problem otwarty. Podobnie, nie jesteśmy w stanie udowodnić że problem SAT nie posiada algorytmu wielomianowego. Zamiast tego, dowodzi się, że SAT jest NP-trudny. Zrobimy dokładnie tak samo: pokażemy, że aproksymacja pewnego problemu jest NP-trudna (tzn. gdybyśmy potrafili aproksymować, to rozwiązalibyśmy w czasie wielomianowym wszystkie problemy z NP). Rozważymy tu słynny problem komiwojażera.

    Problem komiwojażera (Traveling Salesman Problem, TSP)
    Dane: Graf pełny nieskierowany \(G=(V,E)\) oraz funkcja wagowa \(w := E \rightarrow \mathbb{N}\).
    Problem: Znaleźć cykl Hamiltona w \(G\) o najmniejszej całkowitej wadze (waga cyklu \(C\) to \(w(C)=\sum_{e\in E(C)}w(e)\)).

    Przez \(n\) będziemy oznaczać liczbę wierzchołków grafu \(G\).

    Twierdzenie 2
    Dla dowolnej funkcji \(\alpha : \mathbb{N}\rightarrow \mathbb{N}\) obliczalnej w czasie wielomianowym (np. \(\alpha(n)=100\), \(\alpha(n)=\log n\), \(\alpha(n)=n^5\), \(\alpha(n)=2^n\), ale \(\alpha(n)=2^{2^n}\) już nie), nie istnieje algorytm \(\alpha(n)\)-aproksymacyjny dla problemu komiwojażera, o ile \({\rm P}\ne {\rm NP}\).

    Dowód
    Załóżmy, że algorytm o którym mowa istnieje (nazwijmy go ,,algorytm \(A\)''). Pokażemy, że \(A\) mógłby zostać użyty do rozwiązania problemu cyklu Hamiltona w czasie wielomianowym. W problemie cyklu Hamiltona na wejściu pojawia się graf nieskierowany \(G=(V,E)\). Zbudujemy na tej podstawie egzemplarz problemu komiwojażera: będzie to graf pełny \(G'\) o zbiorze wierzchołków \(V\). Pozostaje określić funkcję \(w\):
    \[ w(uv) = \left\{ \begin{array}{ll} 1& \mbox{gdy } uv \in E \\ n\cdot\alpha(n) & \mbox{w p. p. }\end{array} \right. \]
    Zauważmy, że funkcję \(w\) możemy reprezentować w pamięci wielomianowej względem rozmiaru \(G\). Na tak określonym egzemplarzu uruchamiamy algorytm \(A\).

    Wówczas, jeśli \(G\) ma cykl Hamiltona to w naszym egzemplarzu problemu komiwojażera \({\rm OPT} = n\), a więc \(A\) zwróci rozwiązanie o koszcie co najwyżej \(\alpha(n) \cdot n\). Jeśli natomiast \(G\) nie zawiera cyklu Hamiltona to każdy cykl Hamiltona w \(G'\) zawiera krawędź o wadze \(n\cdot\alpha(n)\), a więc cały taki cykl ma wagę większą niż \(n\cdot\alpha(n)\), czyli \(A\) zwróci rozwiązanie o koszcie większym niż \(n\cdot\alpha(n)\).

    Powyżej udowodniliśmy, że \(G\) ma cykl Hamiltona wtedy i tylko wtedy gdy \(A\) zwróci rozwiązanie o koszcie co najwyżej \(\alpha(n) \cdot n\), a to możemy sprawdzić w czasie wielomianowym. Ponieważ problem cyklu Hamiltona jest NP-zupełny, więc \({\rm P}= {\rm NP}\), sprzeczność z założeniem. ♦

    Metryczny problem komiwojażera: 2-aproksymacja

    Pokazując trudność problemu komiwojażera, nie zakładaliśmy niczego o funkcji wagowej \(w\). Tymczasem w wielu zastosowaniach, w szczególności np. jeśli myślimy o podróżowaniu komiwojażera między "prawdziwymi" miastami na powierzchni ziemi, funkcja \(w\) spełnia nierówność trójkąta, tzn. dla dowolnej trójki wierzchołków \(a\), \(b\), \(c\), mamy
    \[w(ab) \le w(ac) + w(cb).\]
    Rozważymy teraz wariant problemu komiwojażera, w którym zakładamy, że \(w\) spełnia nierówność trójkąta (innymi słowy \(w\), określona na parach wierzchołków zamiast na krawędziach, jest metryką).

    Łatwo sprawdzić, że funkcja skonstruowana w dowodzie twierdzenia 2 nie jest metryką, a więc dla takich egzemplarzy nie wykluczyliśmy algorytmów aproksymacyjnych. Okazuje się, że stosunkowo prosto można otrzymać 2-aproksymację. W algorytmie będziemy korzystali z następującego, dobrze znanego, twierdzenia. (Dowód możemy znaleźć np. w wykładzie z matematyki dyskretnej; czytelnik może sprawdzić, że algorytm wynikający z dowodu można zaimplementować w czasie liniowym.)

    Twierdzenie
    W spójnym multigrafie istnieje cykl Eulera wtedy i tylko wtedy, gdy stopień każdego wierzchołka jest parzysty. Co więcej, jeśli cykl Eulera istnieje, można go znaleźć w czasie liniowym.

    Algorytm 2-aproksymacyjny
    1. Znajdź minimalne drzewo rozpinające \(T\) grafu \(G\), np. algorytmem Kruskala
    2. Zastąp każdą krawędź \(T\) parą krawędzi: otrzymujemy spójny multigraf \(T_2\) o parzystych stopniach wierzchołków.
    3. Znajdź cykl Eulera \(\mathcal{E}=v_1v_2\ldots v_{|E(T_2)|}v_1\) w grafie \(T_2\).
    4. Wypisz wierzchołki grafu \(G\) w kolejności ich pierwszych wystąpień w ciągu \(v_1,v_2,\ldots,v_{|E(T_2)|}\)

    Przypomnijmy, że algorytm Kruskala w naszym grafie zadziała w czasie \(O(|E(G)| \log |E(G)|) = O(n^2 \log n)\), natomiast cykl Eulera można znaleźć w czasie liniowym. Całkowita złożoność algorytmu wynosi więc \(O(n^2 \log n)\), a więc jest wielomianowa.

    Twierdzenie Powyższy algorytm jest 2-aproksymacyjny.
    Dowód W ostatnim kroku algorytmu wypisywana jest pewna permutacja wierzchołków. Ponieważ \(G\) jest grafem pełnym, więc ta permutacja wyznacza kolejne wierzchołki pewnego cyklu Hamiltona \(C\) w \(G\), a więc algorytm zawsze zwraca rozwiązanie dopuszczalne.

    Rozważmy dowolne rozwiązanie optymalne OPT. Jest to cykl Hamiltona, a więc w szczególności spójny podgraf rozpinający grafu \(G\) (podgraf rozpinający grafu \(G\) to taki, który zawiera wszystkie wierzchołki \(G\)). Nie jest więc lżejszy niż najlżejszy spójny podgraf rozpinający (który musi być drzewem), a więc:

    \[w(T) \le OPT.\]

    Stąd \(w(\mathcal{E})=w(T_2)=2w(T) \le 2OPT\). Nasz algorytm dzieli \(\mathcal{E}\) na krawędziowo rozłączne marszruty, o końcach w kolejnych wypisywanych w ostatnim kroku wierzchołkach. Każdej takiej marszrucie \(v_{i_1}\ldots v_{i_j}\) odpowiada krawędź \(v_{i_1}v_{i_j}\) w zwracanym w ostatnim kroku algorytmu cyklu Hamiltona \(C\). Z nierówności trójkąta \(w(v_{i_1} v_{i_j}) \le \sum_{k=1}^{j-1}w(v_{i_k} v_{i_{k+1}})\), więc \(w(C) \le w(\mathcal{E}) \le 2OPT \).♦

    Zauważmy, że w tym algorytmie, podobnie jak to miało miejsce w przypadku pokrycia wierzchołkowego, korzystaliśmy z pośrednika: tym razem było to minimalne drzewo rozpinające. Jest to dość naturalny i często używany sposób uzyskiwania pośredników, zwany relaksacją (rozluźnieniem). Chodzi o to, że rozluźniamy warunki dotyczące poszukiwanego obiektu tak, aby uzyskać problem wielomianowy. W tym przypadku szukamy najlżejszego cyklu Hamiltona, natomiast cykle Hamiltona są szczególnym przypadkiem spójnych grafów rozpinających oraz najlżejszy spójny podgraf rozpinający możemy znaleźć w czasie wielomianowym. Oczywiście jeśli warunki są rozluźnione, to minimalny (względem naszego kosztu) obiekt w szerszym zbiorze będzie miał koszt mniejszy lub równy kosztowi rozwiązania optymalnego oryginalnego problemu: dostaniemy więc obiekt lepszy niż OPT, który następnie (tracąc na koszcie) możemy próbować przekształcić do rozwiązania dopuszczalnego.

    Metryczny problem komiwojażera: (3/2)-aproksymacja

    Okazuje się, że zaprezentowany powyżej algorytm 2-aproksymacyjny można stosunkowo łatwo poprawić. Zmodyfikujemy jedynie jeden jego fragment: krok 2. Zauważmy, że celem kroku 2 jest dodanie krawędzi do drzewa rozpinającego \(T\) tak, aby otrzymać graf, w którym wszystkie wierzchołki mają parzyste stopnie. Dodatkowo chcemy, żeby waga dodanych krawędzi była niewielka. Poprzednio, podwajaliśmy stopień każdego wierzchołka w \(T\). Tym razem spróbujmy być bardziej oszczędni: pozostawmy wierzchołki, które już mają parzysty stopień bez zmian. Niech \(N\) będzie zbiorem wierzchołków nieparzystego stopnia w drzewie \(T\). Przypomnijmy treść lematu o uściskach dłoni:

    \[\sum_{v\in V}\deg(v) = 2|E|.\]

    Stąd, stosując powyższą równość do drzewa \(T\), widzimy, że \(|N|\) jest parzyste. Wystarczy więc połączyć wierzchołki zbioru \(N\) w pary, i dla każdej pary dodać do \(T\) łączącą je krawędź. Dodatkowo, chcemy to zrobić najtaniej jak się da: innymi słowy, chcemy znaleźć najlżejsze skojarzenie doskonałe (tzn. łączące wszystkie wierzchołki) w podgrafie grafu \(G\) indukowanym przez zbiór \(N\): taki podgraf oznaczamy przez \(G[N]\). Z pomocą przychodzi nam następujące twierdzenie (w wykładzie o przepływach udowodniliśmy je w wersji dla grafów dwudzielnych).

    Twierdzenie (Edmonds 1965)
    Skojarzenie doskonałe o najmniejszym koszcie można znaleźć w czasie wielomianowym.

    W takim razie, krok 2. algorytmu zastępujemy przez:

    Modyfikacja algorytmu
    2. Znajdź najlżejsze skojarzenie doskonałe \(M\) w grafie \(G[N]\). Niech \(T_2:= T\cup M\).

    Pozostaje jeszcze pokazać, że skojarzenie \(M\) ma małą wagę względem wagi rozwiązania optymalnego problemu komiwojażera.

    Lemat
    Niech \(G=(V,E)\) będzie grafem pełnym z funkcją wagową \(w:E\rightarrow\mathbb{R}\) spełniającą nierówność trójkąta. Niech \(X\) będzie dowolnym podzbiorem \(V\) o parzystej mocy oraz niech \(M\) będzie najlżejszym skojarzeniem doskonałym w grafie \(G[X]\). Jeśli \({\rm OPT}\) jest najlżejszym cyklem Hamiltona w \(G\), to
    \[w(M) \le \frac{1}{2} w({\rm OPT}).\]

    Dowód
    Niech \({\rm OPT}_X\) będzie dowolnym najlżejszym cyklem Hamiltona w \(G[X]\). Ponieważ \({\rm OPT}_X\) ma parzystą długość, więc jest sumą dwóch rozłącznych skojarzeń \({\rm OPT}_X = M_1 \cup M_2\). Stąd \(w({\rm OPT}_X) = w(M_1) + w(M_2) \ge 2 w(M)\), czyli (*)
    \[w(M) \le \frac{1}{2}w({\rm OPT}_X).\]
    Rozważmy teraz cykl Hamiltona \(C=e_1\ldots e_{|X|}\) w \(G[X]\), w którym wierzchołki zbioru \(X\) odwiedzane są w takiej samej kolejności jak w cyklu \({\rm OPT}\). Cykl Hamiltona \({\rm OPT}\) możemy podzielić na rozłączne ścieżki \({\rm OPT}=p_1p_2\ldots p_{|X|}\) tak, że dla każdego \(i=1,\ldots,|X|\) ścieżka \(p_i\) łączy końce krawędzi \(e_i\). Z nierówności trójkąta łatwo pokazać, że \(w(e_i) \le w(p_i)\). Stąd,
    \[w({\rm OPT})=\sum_{i=1}^{|X|}w(p_i) \ge \sum_{i=1}^{|X|}w(e_i) = w(C) \ge w({\rm OPT}_X),\]
    gdzie ostatnia nierówność wynika z tego, że \({\rm OPT}_X\) jest najlżejszym cyklem Hamiltona w \(G[X]\). Otrzymaliśmy więc \(w({\rm OPT}_X)\le w({\rm OPT})\), co razem z (*) daje tezę.

    Na podstawie powyższego lematu otrzymujemy, że \(w(T_2) = w(T) + w(M) \le w({\rm OPT}) + \frac{1}{2}w({\rm OPT}) = \frac{3}{2}w({\rm OPT})\). Jeśli dokończymy analizę tak jak w przypadku algorytmu 2-aproksymacyjnego, dostaniemy poniższe twierdzenie.

    Twierdzenie (Christofides 1976)
    Istnieje algorytm (3/2)-aproksymacyjny dla metrycznego problemu komiwojażera.

    Warto zaznaczyć, że do dziś (2011r) nie jest znany algorytm o mniejszym współczynniku aproksymacji i stworzenie takiego algorytmu jest jednym z najważniejszych problemów otwartych w dziedzinie algorytmów aproksymacyjnych. W 2010r. udało się uzyskać lepsze algorytmy (aktualny rekord to 13/9=1,(4)) dla szczególnego przypadku, gdy \(w\) jest metryką najkrótszych ścieżek w nieskierowanym grafie bez wag.

    Algorytmy aproksymacyjne 2: algorytmy oparte na programowaniu liniowym

    Program liniowy jako ,,pośrednik'' między algorytmem a rozwiązaniem optymalnym

    W poprzednim wykładzie poznaliśmy algorytm 2-aproksymacyjny dla problemu pokrycia wierzchołkowego. Bardzo naturalna jest ważona wersja tego problemu, w której dodatkowo dana jest funkcja \(w:V\rightarrow \mathbb{R}\), tzn. każdy wierzchołek \(v\) ma swoją wagę \(w(v)\). Celem jest oczywiście znalezienie pokrycia wierzchołkowego o najmniejszej możliwej wadze (tzn. sumie wag wierzchołków pokrycia). Czy dla tak uogólnionego problemu również jesteśmy w stanie podać algorytm aproksymacyjny? Pamiętamy, że kluczowym krokiem podczas budowania takiego algorytmu było znalezienie tzw. pośrednika, tzn. obiektu, który możemy znaleźć w czasie wielomianowym, oraz takiego, że jego koszt (waga) jest mniejszy niż koszt rozwiązania optymalnego, a równocześnie możemy na jego podstawie uzyskać rozwiązanie dopuszczalne, przy pewnej stracie na koszcie. W prostszej wersji problemu pośrednikiem było maksymalne skojarzenie. Jakiego pośrednika użyjemy tym razem? W algorytmie dla metrycznego problemu komiwojażera wspomnieliśmy, że często pośrednika uzyskujemy rozluźniając wymagania dotyczące poszukiwanego obiektu. Aby pójść tym tropem wystarczy już tylko skojarzyć dwa fakty: wiele problemów optymalizacyjnych możemy wyrazić jako całkowitoliczbowe programy liniowe, natomiast po usunięciu warunku całkowitoliczbowości otrzymujemy program liniowy (zwany relaksacją), który możemy rozwiązać w czasie wielomianowym. Oto całkowitoliczbowy program liniowy dla problemu ważonego pokrycia wierzchołkowego:

    Program (IWVC)
    \[
    \label{eq:vc-3}
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} w(v)x_v \\
    & x_u+x_v \ge 1 & \forall uv \in E\\
    & x_v \in \{0,1\} & \forall v \in V.
    \end{array}
    \]

    Po opuszczeniu warunku całkowitoliczbowości otrzymujemy program liniowy:

    Program (WVC0)
    \[
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} w(v)x_v \\
    & x_u+x_v \ge 1 & \forall uv \in E\\
    & x_v \ge 0 & \forall v \in V.\\
    & x_v \le 1 & \forall v \in V.
    \end{array}
    \]

    Możemy zauważyć, że warunek \(x_v \le 1\) jest tu zbędny ponieważ nawet jeśli zostanie usunięty, to w każdym rozwiązaniu optymalnym i tak będzie on spełniony. Otrzymujemy więc następujący program:

    Program (WVC)
    \[
    \begin{array}{rll}
    \textrm{min} & \sum_{v\in V} w(v)x_v \\
    & x_u+x_v \ge 1 & \forall uv \in E\\
    & x_v \ge 0 & \forall v \in V.
    \end{array}
    \]

    Przez \({\rm OPT}\) będziemy oznaczać zawsze rozwiązanie optymalne oryginalnego problemu (programu całkowitoliczbowego), natomiast przez \({\rm OPT}_f\) tzw. rozwiązanie ułamkowe, czyli rozwiązanie optymalne relaksacji programu całkowitoliczbowego. O ile nie będzie to prowadzić do niejednoznaczności, tymi samymi symbolami będziemy oznaczać koszty tych rozwiązań. W przypadku problemów minimalizacji prawdziwa jest nierówność
    \[{\rm OPT}_f \le {\rm OPT}.\]

    Widzimy więc, że ,,pośrednikiem'' może być rozwiązanie optymalne programu (WVC).

    Zaokrąglanie w problemie ważonego pokrycia wierzchołkowego

    Rozwiązanie \({\rm OPT}_f\) możemy znaleźć w czasie wielomianowym algorytmem Khachiyana lub Karmarkara. Pozostaje jedynie przekształcić je do rozwiązania dopuszczalnego oryginalnego problemu. Oznaczmy wartości zmiennych \({\rm OPT}_f\) przez \(x^*_v\) dla \(v\in V\). Spróbujmy zastosować pierwszy pomysł jaki się tu narzuca: zaokrąglimy wartości zmiennych \(x^*_v\). Przyjmijmy, że \(x_v = 1\) gdy \(x^*_v \ge \frac{1}{2}\) oraz \(x_v = 0\) w przeciwnym przypadku. Nasz algorytm zwraca zbiór \(A = \{v \in V\ : \ x_v = 1\}\).

    Lemat \(A\) jest pokryciem wierzchołkowym.
    Dowód Rozważmy dowolną krawędź \(uv \in E\) i załóżmy, że nie jest ona pokryta, tzn. \(\{u,v\} \cap A = \emptyset\). Wówczas \(x^*_u < \frac{1}{2}\) oraz \(x^*_v < \frac{1}{2}\), co implikuje \(x^*_u + x^*_v < 1\), a więc rozwiązanie \({\rm OPT}_f\) nie jest dopuszczalne, sprzeczność ♦

    Pozostaje jedynie oszacować jak bardzo zwiększyła się funkcja celu podczas operacji zaokrąglania zmiennych. Zauważmy, że dla dowolnego wierzchołka \(v\), \(x_v \le 2 x^*_v\). Stąd,

    \[w(A) = \sum_{v\in V} w(v)x_v \le \sum_{v\in V} 2 w(v)x^*_v = 2 {\rm OPT}_f \le 2 {\rm OPT}.\]

    Wniosek Istnieje algorytm 2-aproksymacyjny dla problemu ważonego pokrycia wierzchołkowego.

    Zaokrąglanie w problemie ważonego pokrycia zbioru

    Efekt, jaki uzyskaliśmy jest dość zdumiewający: korzystając z programowania liniowego, niemal automatycznie dostaliśmy algorytm 2-aproksymacyjny dla nowej, trudniejszej wersji problemu. Co więcej, wydaje się, że użyta metoda jest dość uniwersalna i może mieć zastosowanie w wielu innych problemach. Dla przykładu, rozważmy problem pokrycia zbioru:

    Problem pokrycia zbioru
    Dane: Zbiory \(S_1, \ldots, S_m\) oraz ich koszty \(w:\{1,\ldots,m\} \rightarrow \mathbb{R}\).
    Problem: Niech \(U=\bigcup_{i=1}^m S_i\). Należy pokryć \(U\) zbiorami \(S_i\) w najtańszy możliwy sposób, tzn. znaleźć zbiór indeksów \(I \subseteq \{1,\ldots,m\}\) taki, że \(U=\bigcup_{i\in I} S_i\) oraz \(\sum_{i\in I}w(i)\) jest najmniejsze możliwe.

    Powyższy problem jest oczywiście NP-trudny, gdyż pokrycie wierzchołkowe jest jego szczególnym przypadkiem: wystarczy przyjąć \(U = E\) oraz dla każdego wierzchołka \(v \in V\) określić zbiór \(S_v\) złożony ze wszystkich krawędzi incydentnych z \(v\). Problem pokrycia zbioru również możemy wyrazić jako całkowitoliczbowy program liniowy:

    Program (IWSC)
    \[
    \begin{array}{rll}
    \textrm{min} & \sum_{i=1}^m w(i)x_i \\
    & \sum_{i\ :\ S_i \ni e} x_i \ge 1 & \forall e \in U\\
    & x_i \in \{0,1\} & \forall i=1,\ldots,m.
    \end{array}
    \]

    Jego relaksacja wygląda następująco:

    Program (WSC)
    \[
    \begin{array}{rll}
    \textrm{min} & \sum_{i=1}^m w(i)x_i \\
    & \sum_{i\ :\ S_i \ni e} x_i \ge 1 & \forall e \in U\\
    & x_i \ge 0 & \forall i=1,\ldots,m.
    \end{array}
    \]

    Spróbujmy postępować analogicznie jak poprzednio: nasz algorytm zaczyna od znalezienia rozwiązania optymalnego \({\rm OPT}_f=(x^*_1,\ldots,x^*_m)\) programu (WSC). Tym razem proste zaokrąglenie do najbliższej liczby całkowitej nie wystarcza aby otrzymać pokrycie, czyli rozwiązanie dopuszczalne programu (IWSC): jest tak dlatego, że dla elementu \(u \in U\) może się okazać, że dla każdego zbioru \(S_i\) zawierającego \(u\) mamy \(x^*_i < \frac{1}{2}\). Pokrycie wierzchołkowe to szczególny przypadek pokrycia zbioru, w którym każdy element należy do dokładnie 2 zbiorów. Wówczas, z lewej strony nierówności \(\sum_{i\ :\ S_i \ni e} x^*_i \ge 1\) mamy tylko 2 zmienne i jedna z nich musi być równa co najmniej \(\frac{1}{2}\). Analogicznego argumentu moglibyśmy użyć w problemie pokrycia zbioru, gdyby liczba zbiorów zawierających dany element była ograniczona przez jakąś niewielką stałą, powiedzmy stałą \(f\). Wówczas dla dowolnego \(u\in U\) mamy pewność, że dla pewnego zbioru \(S_i\) zawierającego \(u\) mamy \(x^*_i \ge \frac{1}{f}\). Zaokrąglając takie zmienne do 1, a pozostałe do 0, otrzymamy rozwiązanie dopuszczalne.

    Algorytm \(f\)-aproksymacyjny dla problemu ważonego pokrycia zbioru
    1. Znajdź rozwiązanie optymalne \(\mathbb{x}^* = (x^*_1,\ldots,x^*_m)\) programu (WSC).
    2. Dla każdego \(i=1,\ldots,m\) przyjmij:
      \[x_i = \left\{ \begin{array}{cl}
      1 & \text{gdy \(x^*_i \ge \frac{1}{f}\)} \\
      0 & \text{w p.p.}
      \end{array} \right.
      \]
    3. Zwróć pokrycie \(I = \{i \ :\ x_i = 1\}\).

    Analogicznie jak poprzednio, \(x_i \le f x^*_i\), więc szacujemy

    \[\sum_{i\in I}w(i) = \sum_{i=1}^m w(i)x_i \le \sum_{i=1}^m f w(i)x^*_i = f {\rm OPT}_f \le f {\rm OPT}.\]

    Wniosek
    Jeśli każdy element zbioru \(U\) występuje w co najwyżej \(f\) zbiorach \(S_i\), to powyższy algorytm jest \(f\)-aproksymacyjny.

    Czy tworząc jakąś bardzo pomysłową regułę zaokrąglania lub może za pomocą zupełnie innego podejścia możemy uzyskać algorytm aproksymacyjny ze współczynnikiem aproksymacji ograniczonym przez stałą, niezależnym od \(f\)? Niestety, okazuje się, że jest to najprawdopodobniej niemożliwe, zachodzi bowiem następujące twierdzenie, którego dowód znacznie wykracza poza zakres naszego wykładu:

    Twierdzenie (Raz, Safra 1997) Nie istnieje algorytm \(o(\log|U|)\)-aproksymacyjny dla problemu pokrycia zbioru, o ile \({\rm P} \ne {\rm NP}\).

    W tym wykładzie zobaczyliśmy, że programy liniowe -- relaksacje odpowiednich programów całkowitoliczbowych, mogą być punktem wyjścia do rozważań o algorytmach aproksymacyjnych. Istotnie, obecnie dla większości problemów najlepsze algorytmy uzyskuje się stosując metody oparte na programowaniu liniowym. W tym wykładzie poznaliśmy przykład zastosowania jednej z takich metod: zaokrąglanie, tzn. zamiana rozwiązania ułamkowego na całkowitoliczbowe. Konkretny algorytm zaokrąglania zależy od problemu: bardzo rzadko jest to zwykłe zaokrąglenie do najbliższej liczby całkowitej.

    Metoda prymalno-dualna

    Z pomocą programowania liniowego udało nam się pokazać, że problem pokrycia wierzchołkowego ma 2-aproksymację nawet w wersji ważonej. Niemniej, z tym uogólnieniem związane są pewne koszty: w wersji nieważonej nasz algorytm wymagał jedynie znalezienia maksymalnego ze względu na zawieranie skojarzenia, co można trywialnie zrealizować w czasie liniowym, tymczasem metoda zaokrąglania w wersji ważonej wymaga użycia skomplikowanego algorytmu do rozwiązywania programów liniowych, którego czas działania wyraża się wielomianem dość dużego stopnia. Pojawia się pytanie, czy w wersji ważonej możliwe jest uzyskanie 2-aproksymacji za pomocą prostszego (a może przy okazji również szybszego) algorytmu. Okazuje się, że jest to możliwe, niemniej jednak teoria programowania liniowego dalej będzie odgrywać kluczową rolę. Pomocny okaże się program dualny do programu (WVC):

    Program (D-WVC)
    \[
    \begin{array}{rll}
    \textrm{max} & \sum_{e \in E} y_e \\
    & \sum_{vw\in E}y_{vw} \le w(v) & \forall v \in V\\
    & y_e \ge 0 & \forall e \in E.\\
    \end{array}
    \]

    Zaczniemy od następującej obserwacji.

    Obserwacja 1
    Niech \(y\) będzie dowolnym rozwiązaniem dopuszczalnym programu (D-WVC). Wówczas, dla dowolnej krawędzi \(e = uv \in E\), zachodzi co najmniej jeden z poniższych warunków:

    1. \(\sum_{vw\in E}y_{vw} = w(v)\),
    2. \(\sum_{uw\in E}y_{uw} = w(u)\),
    3. po powiększeniu \(y_e\) o \(\min\{w(v) - \sum_{vw\in E}y_{vw}, w(u) - \sum_{uw\in E}y_{uw}\}\) otrzymamy nowe rozwiązanie dopuszczalne.

    Obserwacja 1 sugeruje następujący prosty algorytm:

    Algorytm prymalno-dualny
    1. \(\mathbf{y} := \mathbf{0}\) (początkowe, zerowe rozwiązanie dopuszczalne programu (D-WVC))
    2. Dopóki istnieje krawędź \(e\) taka, że żaden z warunków 1 i 2 nie jest spełniony (tzn. \(\sum_{vw\in E}y_{vw} < w(v)\) oraz \(\sum_{uw\in E}y_{uw} < w(u)\)), zmodyfikuj \(\mathbf{y}\), zgodnie z warunkiem 3.
    3. Zwróć zbiór \(S = \{v \in V\ :\ \sum_{vw \in E}y_{vw} = w(v)\}\).

    Z obserwacji 1 jest oczywiste, że algorytm zwraca pokrycie. Ponieważ przy każdym obrocie pętli co najmniej 1 nowa nierówność staje się równością (a te nierówności, które już były spełnione z równością dalej takimi pozostają), wykona się co najwyżej \(|V|\) obrotów pętli. Cały algorytm można łatwo zaimplementować w czasie \(O(|V|+|E|)\).

    Nazwa ,,algorytm prymalno-dualny'' bierze się stąd, że możemy przyjąć, że w każdym momencie działania algorytmu, oprócz rozwiązania \(\mathbf{y}\) programu dualnego określone jest również rozwiązanie \(\mathbf{x}\) programu prymalnego (używamy notacji Iversona):
    \[x_v = [\sum_{vw \in E}y_{vw} = w(v)].\]
    Algorytm utrzymuje więc dopuszczalne rozwiązanie programu dualnego i całkowitoliczbowe, ale niekoniecznie dopuszczalne, rozwiązanie programu prymalnego. Niespełniony warunek programu prymalnego podpowiada, którą zmienną dualną należy zmienić. Z kolei warunek programu dualnego, który staje się spełniony z równością określa zmienną prymalną, która zmienia swoją wartość. W ten sposób algorytm na zmianę znajduje rozwiązania programu dualnego i prymalnego, jedno ,,coraz bardziej optymalne'', drugie ,,coraz bardziej dopuszczalne''. W chwili gdy rozwiązanie prymalne staje się dopuszczalne, algorytm się kończy.

    Pozostaje oszacować wagę uzyskanego pokrycia wierzchołkowego:

    \[w(S)=\sum_{v \in S} w(v) = \sum_{v \in S} \sum_{vw\in E}y_{vw} \le 2 \sum_{e \in E} y_{e} \le 2 {\rm OPT}.\]

    Pierwsza nierówność bierze się tu stąd, że ponieważ krawędź ma tylko 2 końce, więc dla każdej krawędzi \(e\) zmienna \(y_e\) pojawi się w podwójnej sumie co najwyżej 2 razy. Druga nierówność bierze się natomiast ze słabej dualności: \(\sum_{e \in E} y_{e}\) jest funkcją celu dla rozwiązania dopuszczalnego \(\mathbf{y}\) programu dualnego, a optymalne pokrycie, czyli rozwiązanie optymalne programu (IWVC) jest rozwiązaniem dopuszczalnym programu prymalnego (WVC).

    Na powyższe szacowanie warto patrzeć w następujący, intuicyjny sposób. Pokrycie \(S\) jest opłacone przez sumę wszystkich zmiennych dualnych znajdujących się w warunkach spełnionych z równością. Każda zmienna dualna pojawia się w dwóch warunkach, a więc płaci co najwyżej dwa razy. Stąd, całkowity koszt nie przekracza \(2 \sum_{e\in E} y_e\). Taki ,,mechanizm lokalnych opłat'' pojawia się w wielu algorytmach aproksymacyjnych.

    Wniosek Algorytm prymalno-dualny jest 2-aproksymacyjny.

    Warto zauważyć, że w algorytmie prymalno-dualnym użyliśmy nowego ,,pośrednika'': było to rozwiązanie dopuszczalne programu dualnego. Dzięki temu, że nie potrzebowaliśmy rozwiązania optymalnego programu dualnego, nasz algorytm jest bardzo prosty w implementacji.

    A teraz zagadka: Jaki algorytm otrzymamy, jeśli zastosujemy algorytm prymalno-dualny w sytuacji, gdy wszystkie wagi wierzchołków są równe 1 (czyli de facto w nieważonej wersji problemu pokrycia wierzchołkowego)?

    Metoda prymalno-dualna została z sukcesem zastosowana w wielu problemach. Historycznie pierwszym takim problemem był problem najtańszego skojarzenia w grafie dwudzielnym (był to algorytm dokładny, a nie aproksymacyjny). Zachęcamy czytelnika do rozwiązania poniższego zadania.

    Zadanie Opisz algorytm prymalno-dualny dla problemu ważonego pokrycia zbioru i udowodnij, że jest on \(f\)-aproksymacyjny.

    Haszowanie 1: łańcuchowanie, rodziny uniwersalne i niezależne

    Zajmiemy się jednym z podstawowych problemów algorytmicznych: problemem słownika. W tym rozdziale omawiamy jedno z bardziej efektywnych podejść do tego problemu, zwane haszowaniem (warto wspomnieć, że problem słownika jest głównym, ale nie jedynym zastosowaniem haszowania). Przypomnijmy krótko na czym polega problem słownika.

    Problem słownika

    Należy opisać strukturę danych, która reprezentuje pewien zbiór elementów \(S\) i udostępnia następujące operacje:

    • Lookup(\(x\)) -- czy \(x \in S\)?
    • Insert(\(x\)) -- \(S:=S \cup \{x\}\)
    • Remove(\(x\)) -- \(S:=S \setminus \{x\}\)

    Zakładamy, że elementy zbioru \(S\) pochodzą z pewnego uniwersum \(U\), tzn. \(S\subseteq U\). W zastosowaniach elementy \(S\) są zwykle liczbami lub napisami. W dalszych rozważaniach będziemy przyjmować, że \(U\) jest zbiorem liczb naturalnych z ograniczonego zakresu, tzn. \(U=\{0,\ldots,|U|-1\}\). Nie jest to duże ograniczenie, gdyż w ten sposób możemy reprezentować także np. napisy o ograniczonej długości czy liczby wymierne o ograniczonej precyzji.

    Będziemy również zakładać, że znana jest liczba \(n\) taka, że \(|S|\le n\).

    Haszowanie: główny pomysł

    W znanych nam drzewiastych rozwiązaniach problemu słownika, chcąc wyszukać element w strukturze danych, algorytm "błądzi" po strukturze danych podążając za kolejnymi wskaźnikami. W haszowaniu chcemy skrócić ten proces: z grubsza rzecz biorąc, dla każdego potencjalnego elementu \(x\) chcielibyśmy natychmiast (ewentualnie wykonując proste obliczenia w czasie stałym) poznać miejsce, w którym on się znajduje. W najprostszej wersji, mamy tablicę haszującą

    \(T[0, \ldots, m-1],\)

    oraz funkcję haszującą

    \(h: U \longrightarrow \{0,\ldots,m-1\}.\)

    Ponieważ nie chcemy, aby nasza struktura zajmowała istotnie więcej miejsca niż jest to konieczne, zwykle \(m = O(n)\), np. \(m = 2n\) lub \(m = n\). Potencjalnie \(h\) może być dowolną funkcją, choć dla nas użyteczne będą takie, których wartość możemy wyliczyć w czasie stałym. Generalnie, elementu \(x\in U\) będziemy szukać w komórce \(T[h(x)]\). Oczywiście zwykle uniwersum \(U\) jest dużo większe niż \(m\), więc niezależnie od tego jakiej funkcji haszującej \(h\) użyjemy, będzie się zdarzać, że dwa elementy \(x\) i \(y\) zostaną odwzorowane w tę samą komórkę tablicy \(T\), tzn. \(h(x)=h(y)\). Taką sytuację nazywamy konfliktem. Zaczynamy od najprostszego, ale całkiem skutecznego sposobu rozwiązywania konfliktów: haszowania przez łańcuchowanie.

    Haszowanie przez łańcuchowanie

    W haszowaniu przez łańcuchowanie dla każdego \(j\) tablica \(T\) w komórce \(T[j]\) zawiera listę takich elementów \(x\in S\), że \(h(x) = j\). Teraz możemy bardzo łatwo zaimplementować operacje słownikowe: dla danego \(x\) wykonujemy odpowiednią operację na liście \(T[h(x)]\) (Lookup -- sprawdzamy czy lista \(T[h(x)]\) zawiera element; Insert -- wstawienie elementu na listę \(T[h(x)]\); Remove -- usunięcie elementu z listy \(T[h(x)]\)).

    W ten sposób czas wykonania operacji na elemencie \(x\) jest proporcjonalny do długości listy \(T[h(x)]\). Chcielibyśmy jakoś ograniczyć długość list elementów odwzorowanych w tę samą komórkę. Widać, że wszystko zależy od tego, jak dobrze dobierzemy funkcję \(h\). Niektóre funkcje \(h\) są w oczywisty sposób ,,złe'', np. funkcje stałe. Czy istnieją ,,dobre'' funkcje?

    Gdybyśmy znali z góry zbiór \(S\) być może może udałoby się nawet sprawić, aby każdy element \(S\) został odwzorowany w inną komórkę tablicy \(T\) (w dalszej części wykładu zobaczymy, że istotnie jest to możliwe). Problem polega na tym, że w naszym problemie słownika to funkcję \(h\) ustalamy z góry, przed pojawieniem się ciągu operacji i chcielibyśmy, żeby w każdej chwili w przyszłości elementy \(S\) ,,w miarę jednostajnie'' rozproszyły się po \(\{0,\ldots,m-1\}\). Naturalnymi kandydatkami mogą być takie funkcje, które jednostajnie rozpraszają \(U\), tzn. dla dowolnych \(y_1,y_2\in\{0,\ldots,m-1\}\), \(|h^{-1}(y_1)| \approx |h^{-1}(y_2)|\), np. \(h(x) = x \mod m\). Widać, że takie funkcje działałyby dobrze dla losowych danych.

    Często dane, które pojawiają się w praktyce są faktycznie zbliżone do losowych. My jednak stawiamy sobie za cel rozwiązanie, które będzie dobre niezależnie od danych. Od razu spotyka nas rozczarowanie. Zauważmy bowiem, że

    Uwaga
    Dla \(|U|\gg m\), każda funkcja \(h: U \mapsto \{0,\ldots,m-1\}\) jest zła, tzn. złośliwy przeciwnik, jeśli będzie znał \(h\), może tak dobierać elementy dodawane do \(S\), że \(h(x) = h(y)\) dla każdej pary \(x,y \in S\).

    W 1977r Carter i Wegman wpadli na prosty pomysłem na poradzenie sobie z tym problemem. Mianowicie, funkcję haszującą będziemy losowali z pewnej rodziny funkcji -- w ten sposób przeciwnik będzie zmuszony się zabezpieczyć przed całym zbiorem funkcji, a to mu się nie uda. Z pewnym małym prawdopodobieństwem algorytm może wprawdzie dalej działać długo, jednakże to prawdopodobieństwo nie zależy od danych. W następnym rozdziale zastanowimy się, jakie warunki powinny spełniać takie rodziny funkcji i podamy kilka przykładów takich rodzin.

    Uniwersalne i niezależne rodziny funkcji haszujących

    Przykład 1
    Załóżmy, że \(h\) jest losowana jednostajnie ze zbioru wszystkich funkcji, czyli z \(\{0,\ldots,m-1\}^U\). Odpowiada to wylosowaniu niezależnie dla każdego \(x \in U\) wartości \(h(x) \in \{0,\ldots,m-1\}\).
    Zauważmy, że wówczas
    \[
    \mathbb{P}\left[h(x) = h(y) \right] = \left\{ \begin{array}{ll} 1& \mbox{gdy } x = y \\ \frac{1}{m} & \mbox{ gdy } x \neq y .\end{array} \right.
    \]
    Stąd, dla dowolnego \(x\in U\):
    \[
    \mathbb{E} |h(x)| = \mathbb{E}\left[\sum_{y \in S} \mathbf{1}_{h(x) = h(y)} \right]= \sum_{y \in S} \mathbb{P}\left[h(x) = h(y)\right] = \left\{ \begin{array}{ll} \frac{n}{m}& \mbox{gdy } x \notin S \\ 1+ \frac{n-1}{m} & \mbox{gdy } x \in S.\end{array} \right.
    \]
    W powyższych rachunkach korzystamy z liniowości wartości oczekiwanej; notacja \(\mathbf{1}_{A}\) oznacza indykator zdarzenia \(A\), tzn. zmienną losową, która przyjmuje wartość 1 gdy \(A\) się wydarzy i \(0\) w przeciwnym przypadku. Dla \(m = \Omega(n)\) otrzymane wyrażenie jest \(O(1)\), co daje oczekiwany czas stały wszystkich operacji słownikowych. Udało się więc przechytrzyć złośliwego przeciwnika. Ale czy na pewno?

    Powstaje pytanie jak szybko wylosować taką funkcję oraz ile pamięci potrzeba, aby ją reprezentować. Pierwsze, co przychodzi do głowy to reprezentowanie \(h\) jako tablicy \(A[1 \ldots |U|]\) liczb od 0 do \(m-1\). Jeśli jednak mamy do dyspozycji tyle pamięci (i czasu na inicjalizację) to nie musimy używać haszowania -- słownik możemy zaimplementować jako tablicę bitową rozmiaru \(|U|\). Z drugiej strony, jeśli naprawdę uprzemy się, żeby \(h\) losować ze zbioru wszystkich funkcji, lepsze rozwiązanie nie istnieje: do przechowywania \(h\) potrzebujemy co najmniej \(|U|\log_2 m\) bitów (za pomocą mniej niż \(|U|\log_2 m\) bitów możemy reprezentować mniej niż \(2^{|U|\log_2 m}=m^{|U|}\) funkcji, czyli nie wszystkie).

    Przyjrzyjmy się rachunkom z powyższego przykładu i zastanówmy się czy coś da się jeszcze z niego uratować. Zauważmy, że nie potrzebowaliśmy pełnej losowości, a jedynie własności \(\mathbb{P}\left[h(x) = h(y)\right] \leq \frac{1}m\) dla \(x \neq y\). Tę cechę dostrzegli w 1977 roku Carter i Wegman. Podali oni następującą definicję.

    Definicja
    Niech \(\mathcal{H} \subseteq \{0,\ldots,m-1\}^{U}\). Powiemy, że \(\mathcal{H}\) jest rodziną \((\alpha,k)\)-uniwersalną, gdy po wybraniu losowej funkcji \(h \in \mathcal{H}\) (tzn. jednostajnie, każdą z równym prawdopodobieństwem), dla dowolnych parami różnych \(x_1, x_2, \ldots, x_k \in U\) zachodzi
    \[
    \mathbb{P}[h(x_1) = h(x_2) = \ldots = h(x_k)] \leq \frac{\alpha}{m^{k-1}}.
    \]

    Rodzinę \((1,k)\)-uniwersalną nazywamy krótko $k$-uniwersalną, natomiast na rodzinę 2-uniwersalną mówimy jeszcze krócej: rodzina uniwersalna. Z naszych wcześniejszych rozważań wynika, że

    Twierdzenie 2
    Jeśli używamy haszowania łańcuchowego z funkcją haszującą \(h\) wybraną losowo z uniwersalnej rodziny funkcji haszujących, to oczekiwane czasy wszystkich operacji słownikowych są stałe.

    Rozważmy jeszcze jedną naturalną definicję. Mówimy, że rodzina \(\mathcal{H}\) jest rodziną jednostajną, gdy po wybraniu losowej funkcji \(h \in \mathcal{H}\), dla dowolnego \(x \in U\) zmienna losowa \(h(x)\) ma rozkład jednostajny, tzn. dla każdego \(a \in \{0,\ldots,m-1\}\) zachodzi \(\mathbb{P}[h(x)=a] = \frac{1}{m}\). Widzimy, że jednostajność jest również cechą pożądaną, jednakże łatwo sobie uświadomić, że nie gwarantuje ona efektywności operacji słownikowych.

    Zadanie
    Podaj przykład jednostajnej rodziny funkcji haszujących \(\mathcal{H}\) oraz ciągu \(n\) operacji Insert oraz Lookup, który wykona się w czasie \(\Omega(n^2)\) niezależnie od tego, jaka funkcja haszująca \(h\) zostanie wylosowana z \(\mathcal{H}\).

    W niektórych zastosowaniach wygodnie jest korzystać z rodzin funkcji posiadających jeszcze silniejsze własności niż rodziny uniwersalne.

    Definicja
    Niech \(\mathcal{H} \subseteq \{0,\ldots,m-1\}^{U}\).
    Powiemy, że \(\mathcal{H}\) jest rodziną \(k\)-niezależną (lub silnie \(k\)-uniwersalną), gdy po wybraniu losowej funkcji \(h \in \mathcal{H}\), dla dowolnych parami różnych \(x_1, x_2, \ldots, x_k \in U\) i dowolnych \(y_1, y_2, \ldots , y_k \in \{0,\ldots,m-1\}\) zachodzi
    $$
    \mathbb{P}[h(x_1) = y_1 \wedge h(x_2) = y_2 \wedge \ldots \wedge h(x_k) = y_k] = \frac{1}{m^{k}}.
    $$
    Równoważnie możemy to wyrazić jako koniunkcję dwóch warunków (ćwiczenie: pokaż równoważność):

    • (\(k\)-niezależność zmiennych) dla dowolnych parami różnych \(x_1, \ldots, x_k \in U\) zmienne losowe \(h(x_1), \ldots, h(x_k)\) są \(k\)-niezależne
    • (jednostajność) \(\mathcal{H}\) jest jednostajna.

    Zauważmy, że \(k\)-niezależność implikuje \(k\)-uniwersalność (zachęcamy Czytelnika do zweryfikowania tego faktu).

    Podamy teraz konkretne przykłady rodzin \((\alpha,k)\)-uniwersalnych i \(k\)-niezależnych. Zauważmy, że rozważana w przykładzie 1 rodzina wszystkich funkcji jest \(k\)-uniwersalna, a nawet \(k\)-niezależna dla dowolnego \(k\). Przykłady, które za chwilę poznamy, będą jednak bardziej praktyczne, w szczególności będą umożliwiały szybkie wylosowanie funkcji, oraz szybkie obliczenie jej wartości w danym elemencie \(U\).

    Haszowanie tabulacyjne

    W tym rozdziale rozważamy bardzo praktyczną sytuację. Niech \(U = \{0, \ldots, 2^{cr} - 1\}\), dla pewnych \(c,r\in \mathbb{N}\), \(c>1\). Innymi słowy, uniwersum \(U\) składa się z liczb \((cr)\)-bitowych. Każdą taką liczbę \(x\) będziemy reprezentować jako krotkę \((x_1, x_2, \ldots, x_c)\) liczb r-bitowych. W typowych zastosowaniach \(U\) składa się z 32- lub 64-bitowych liczb naturalnych, natomiast \(r=8\), czyli krotka to kolejne bajty liczby \(x\).

    Ponadto zakładamy, że długość tablicy haszującej \(m\) jest potęgą dwójki, \(m = 2^k\). To założenie nie jest zbyt restrykcyjne, gdyż w najgorszym razie tablica haszująca będzie mniej niż 2 razy większa niż potrzeba.

    Oznaczmy \([\ell] = \{0,\ldots, \ell-1\}\). Rozważmy następującą rodzinę funkcji haszujących postaci \(h:U\rightarrow [m]\):

    \[\mathcal{H} = \{ x \mapsto T_1 (x_1) \oplus T_2 (x_2) \oplus \ldots \oplus T_c (x_c)\ |\ T_1,...,T_c : [2^r] \rightarrow [m] \},\]

    gdzie \(\oplus\) oznacza operację xor, np \(6 \oplus 3 = (110)_2 \oplus (011)_2 = (101)_2 = 5.\)

    Przykład 3
    Załóżmy że \(U=\{0,\ldots,2^{32}-1\}\) oraz \(m=2^{16}=65536\). Wybierzmy \(c=4\) (wówczas \(r=8\)). Wówczas każda funkcja \(h\) z rodziny \(\mathcal{H}\) jest reprezentowana za pomocą czterech tablic \(T_1, T_2, T_3, T_4\), z których każda zawiera \(2^8=256\) liczb z przedziału \(\{0,\ldots,m-1\}\). Sumarycznie, do reprezentowania \(h\) potrzebujemy \(4\cdot 256\cdot 2=1024\) bajty. Aby obliczyć wartość \(h(x)\) dla danego \(x\), wykonywane są 3 operacje \(\oplus\) na 4 liczbach odczytanych z tablic. Zajmuje to czas stały, ale warto dodatkowo zwrócić uwagę, że wykonywane operacje są bardzo proste, więc obliczenie wartości \(h(x)\) będzie bardzo szybkie (dodatkowo część obliczeń zostanie zrównoleglonych na współczesnych procesorach). Aby wylosować funkcję z rodziny \(\mathcal{H}\), losujemy 512 liczb z zakresu \(\{0,\ldots, m\}\) i umieszczamy w tablicach \(T_1,\ldots,T_4\).

    Twierdzenie 4
    Rodzina \(\mathcal{H}\) jest \(2\)-niezależna.

    Dowód
    Na początek sprawdźmy, że \(\mathcal{H}\) jest jednostajna, tzn.\ dla każdego \(x\in U\) i \(a\in [m]\), \(\mathbb{P}[h(x)=a]=\frac{1}{m}\). Jest to prawda, gdyż po wylosowaniu \(T_1(x_1),\ldots,T_{c-1}(x_{c-1})\), niezależnie od wyników tych losowań, otrzymamy \(h(x)=a\) gdy \(T_c(x_c)=a\oplus T_1 (x_1) \oplus T_2 (x_2) \oplus \ldots \oplus T_{c-1} (x_{c-1})\), a to zdarzenie ma prawdopodobieństwo \(\frac{1}{m}\). (Formalnie możemy przeprowadzić to rozumowanie korzystając ze wzrou na prawdopodobieństwo całkowite.)

    Rozważmy teraz dwa różne elemety uniwersum \(x,y \in U\), oraz dowolne \(a,b\in[m]\). Z powodu symetrii, bez straty ogólności możemy przyjąć, że \(x_1\ne y_1\). Po wylosowaniu \(T_2(x_2),\ldots,T_c(x_c)\) oraz \(T_2(y_2),\ldots,T_c(y_c)\), niezależnie od wyników tych losowań, otrzymamy \(h(x)=a\) i \(h(y)=b\) wtw gdy \(T_1(x_1)=a\oplus T_2(x_2)\oplus\ldots\oplus T_c(x_c)\) oraz \(T_1(y_1)=b\oplus T_2(y_2)\oplus\ldots\oplus T_c(y_c)\). Ponieważ \(x_1\ne y_1\) te dwa zdarzenia są niezależne, czyli:

    \[\begin{align}
    & \mathbb{P}[h(x)=a \wedge h(y)=b] =\\
    & \mathbb{P}[T_1(x_1)=a\oplus T_2(x_2)\oplus\ldots\oplus T_c(x_c) \wedge T_1(y_1)=b\oplus T_2(y_2)\oplus\ldots\oplus T_c(y_c)]=\\
    & \mathbb{P}[T_1(x_1)=a\oplus T_2(x_2)\oplus\ldots\oplus T_c(x_c)] \cdot \mathbb{P}[T_1(y_1)=b\oplus T_2(y_2)\oplus\ldots\oplus T_c(y_c)]=\\
    & \frac{1}{m}\cdot\frac{1}{m} = \frac{1}{m^2}.
    \end{align}
    \]

    Zadanie
    Udowodnij, że rodzina \(\mathcal{H}\) jest \(3\)-niezależna dla dowolnego \(c\ge 1\), ale nie jest \(4\)-niezależna gdy \(c\ge 2\).


    Rodzina funkcji liniowych

    Niech \(p\) będzie dowolną liczbą pierwszą większą od \(|U|\). Rozważmy następującą rodzinę.
    $$
    \mathcal{H} = \{ x \mapsto \left( (ax+b)\bmod p\right)\bmod m\ |\ a \in \{0,\ldots,p-1\}, b \in \{0,\ldots,p-1\}\}.
    $$

    Zauważmy, że każda funkcja z \(\mathcal{H}\) jest reprezentowana przez zaledwie dwie liczby \(O(\log|U|)\)-bitowe \(a\) i \(b\). Z praktycznego punktu widzenia wygląda to bardziej atrakcyjnie niż reprezentacje funkcji z rodzin tabulacyjnych (np. 1024 bajty w funkcji z przykładu 3). Ta rodzina ma jednak znacznie mniejsze znaczenie praktyczne ze względu na czas obliczania wartości funkcji. Chociaż pozostaje on stały w sensie asymptotycznym, podczas obliczania wartości funkcji wykonywane są kosztowne operacje mnożenia i obliczania reszty z dzielenia przez \(p\) i przez \(m\) (można ten efekt ograniczyć, przyjmując jako \(m\) potęgę dwójki, natomiast jako \(p\) wziąć liczbę pierwszą szczególnej postaci, mimo wszystko eksperymenty pokazują jednak, że obliczanie wartości w haszowaniu tabulacyjnym jest istotnie szybsze).

    Twierdzenie 5
    Rodzina \(\mathcal{H}\) jest \(2\)-uniwersalna.

    Dowód
    Weźmy \(x_1 \neq x_2\) należące do \(U\). Oznaczmy
    \[
    \begin{align*}
    x_1' &:= (ax_1 + b) \bmod p\\
    x_2' &:= (ax_2 + b) \bmod p
    \end{align*}
    \]
    Dzięki temu, że \(p\) jest pierwsza, \(\mathbb{Z}_p\) jest ciałem, a w ciele:
    $$
    ax_1+b = ax_2 + b \iff x_1 = x_2,
    $$
    czyli \(x_1' \neq x_2'\).

    Pokażemy teraz, że dla dowolnych \(i \neq j\) ze zbioru \(\{0,\ldots,p-1\}\) zachodzi (*)
    \[
    \mathbb{P}[x_1' = i \wedge x_2' = j] = \frac{1}{p(p-1)},
    \]
    a więc, że para \((x_1',x_2')\) jest losową parą uporządkowaną różnych liczb z \(\mathbb{Z}_p\).

    Zbiór funkcji haszujących \(\mathcal{H}\) rozmiaru \(p(p-1)\) jest naszą przestrzenią probabilistyczną. Ile jest zdarzeń elementarnych (funkcji haszujących \(h \in \mathcal{H}\), czyli par \((a,b)\)), takich że \(x_1'=i\) oraz \(x_2'=j\)? Każda taka para \((a,b)\) jest wyznaczona przez układ równań
    \[
    \left\{ \begin{array}{ll} ax_1 + b = i \\ ax_2+b = j.\end{array} \right.
    \]
    Ten układ ma jednoznaczne rozwiązanie, ponieważ
    \[\det\left[ \begin{array}{ll} x_1 & 1 \\ x_2 & 1\end{array} \right] \neq 0.\]
    Zatem istnieje dokładnie jedna para spośród \(p(p-1)\), która spełnia układ równań, zatem (*) jest udowodnione.

    Teraz pokażemy, że (**)
    \[\mathbb{P}[x_1' \equiv x_2' \pmod m] \leq \frac{1}m.\]
    Jeśli to zdarzenie zachodzi, to dla pewnych \(k,l\)
    \[
    \left\{ \begin{array}{ll} x_1' = km + r \\ x_2' = lm + r.\end{array} \right.
    \]
    Dla ustalonego \(x_1'\) istnieje co najwyżej \(\lceil\frac{p}m\rceil -1\) liczb \(l\ne k\), które dadzą nam taki \(x_2'\) (odejmujemy 1 bo wiemy, że \(x_1'\ne x_2'\)), zatem sumując po wszystkich \(p\) możliwych wartościach \(x_1'\) dostajemy
    \[\mathbb{P}[x_1' \equiv x_2' \pmod m] \leq p \frac{\lceil\frac{p}m\rceil-1}{p(p-1)} \leq \frac{\frac{p+m-1}m-1}{p-1} = \frac{p-1} {m(p-1)} = \frac{1}m
    \]
    Z (**) wynika, że rodzina~\(\mathcal{H}\) jest rzeczywiście 2-uniwersalna. ♦

    Można pokazać (do czego zachęcamy czytelnika), że rodzina \(\mathcal{H}\) nie jest 2-niezależna.


    Rodzina wielomianów

    W tym punkcie rozważamy uogólnienie rodziny z poprzedniego punktu.
    $$
    \mathcal{H}^m = \{ x \mapsto \left( (a_0 + a_1x + \ldots + a_{k-1}x^{k-1})\bmod p\right)\bmod m\ |\ a_i \in \{0,\ldots,p-1\}\}.
    $$

    Zauważmy, że tym razem każdy ze współczynników \(a_1,\ldots,a_{k-1}\) może być równy 0, a więc \(\mathcal{H}^m\) zawiera funkcje stałe, które w kontekście zastosowań słownikowych zachowują się fatalnie! Taka definicja pozwala jednak uprościć rozumowania (zauważmy ponadto, że prawdopodobieństwo wylosowania funkcji stałej jest bardzo małe).

    Pokażemy, że \(\mathcal{H}^m\) jest \((O(1),k)\)-uniwersalna, a nawet że bardzo niewiele brakuje jej, aby być \(k\)-niezależną (spełniony jest warunek niezależności zmiennych, natomiast rozkład zmiennej \(h(x)\) jest niemal jednostajny), szczególnie jeśli \(p \gg m\).

    Rozważmy parami różne zmienne \(x_1, \ldots, x_k\). Podobnie jak poprzednio niech \(x_i':= \sum_{j} a_jx_i^j \bmod p\) (a więc \(h(x_i) = x_i' \bmod m\)).

    Pokażemy najpierw, że dla dowolnych \(y'_1,\ldots,y'_k \in \{0,\ldots,p-1\}\) zachodzi (***)
    \[\mathbb{P}[\bigcap_{i=1}^k x_i' = y'_i] = \frac{1}{p^k}.\]
    To zdarzenie odpowiada układowi równań w ciele \(\mathbb{Z}_p\):
    \[\left\{ \begin{array}{ccl}
    a_0 + a_1x_1 + \ldots + a_{k-1}x_1^{k-1}& \equiv &y'_1 \pmod p\\
    a_0 + a_1x_2 + \ldots + a_{k-1}x_2^{k-1}& \equiv &y'_2 \pmod p\\
    \vdots&\vdots&\\
    a_0 + a_1x_k + \ldots + a_{k-1}x_k^{k-1}& \equiv &y'_k \pmod p
    \end{array} \right.
    \]
    Macierz
    \[\left[ \begin{array}{cccc}
    1 & x_1 & \ldots & x_1^{k-1} \\
    1 & x_2 & \ldots & x_2^{k-1} \\
    \vdots&\vdots& &\vdots\\
    1 & x_k & \ldots & x_k^{k-1} \\
    \end{array} \right]
    \]
    tego układu jest macierzą Vandermonde'a, a ta ma niezerowy wyznacznik dla parami różnych \(x_1,\ldots,x_k\). Zatem istnieje dokładnie jedno rozwiązanie \((a_0,\ldots,a_{k-1})\) tego układu, a więc równość (***) jest udowodniona.

    W tej chwili zauważmy, że właśnie pokazaliśmy następujący wniosek.

    Wniosek 6
    Rodzina \(\mathcal{H}^p = \{ x \mapsto (a_0 + a_1x + \ldots + a_{k-1}x^{k-1})\bmod p\ |\ a_i \in \{0,\ldots,p-1\}\}\) jest rodziną \(k\)-niezależną.

    W szczególności powyższy fakt implikuje, że jeśli \(h\) wylosowano z \(\mathcal{H}^p\) to zmienne losowe \(h(x)\) dla \(x\in U\) są \(k\)-niezależne. Z tego łatwo wynika (zachęcamy czytelnika do sprawdzenia), że:

    Wniosek 7
    Jeśli \(h\) wylosowano z \(\mathcal{H}^m\) to zmienne losowe \(h(x)\) dla \(x\in U\) są \(k\)-niezależne.

    Aby rodzina \(\mathcal{H}^m\) była \(k\)-niezależna, potrzeba jednak jeszcze, żeby funkcja \(h\) była jednostajna, a to nie do końca jest prawdą.

    Analogicznie jak w poprzednim punkcie, dla ustalonych \(y_1,\ldots,y_k \in \{0,\ldots,m-1\}\) mamy
    \[
    \mathbb{P}[\bigcap_{i=1}^k h(x_i) = y_i] = \mathbb{P}[\bigcap_{i=1}^k x'_i \equiv y_i \pmod m] \leq {\left\lceil \frac{p}m\right\rceil}^k \frac{1}{p^k},
    \]
    ponieważ dla każdego \(y_i\) istnieje co najwyżej \(\lceil \frac{p}m\rceil\) wartości \(x'_i\), że \(x'_i \equiv y_i \mod m\), a każda konkretna krotka jest losowana z prawdopodobieństwem \(\frac{1}{p^k}\) na mocy równości (***).

    Widzimy, że nie dostaliśmy tu oszacowania przez \(\frac{1}{m^k}\), którego wymaga defnicja rodziny \(k\)-niezależnej, jednakże zwykle \(m \ll p\) a więc \({\left\lceil \frac{p}m\right\rceil}^k \frac{1}{p^k}\) jest bardzo bliskie \(\frac{1}{m^k}\). Jeśli np. dobierzemy \(p\) tak, aby \(\frac{m-1}{p} \leq \frac{1}k\), to
    $$
    {\left\lceil \frac{p}m\right\rceil}^k \frac{1}{p^k} \leq \left( \frac{p+m-1}{pm}\right)^k = \left( \frac{1+\frac{m-1}p}{m}\right)^k < \frac{e}{m^k}.
    $$
    Wtedy \(\mathcal{H}^m\) jest \((e,k)\)-uniwersalna.


    Bardziej praktyczna rodzina funkcji liniowych

    W praktyce rozmiar uniwersum i \(m\) są potęgami dwójki: \(U = \{0,\ldots,2^k-1\}\), \(m=2^l\). Wtedy \(h: \{0,\ldots,2^k-1\} \longrightarrow \{0,\ldots,2^l-1\}\). Dietzfelbinger zaproponował następującą rodzinę funkcji haszujących:
    $$
    \mathcal{H}_{k,l} = \{ x \mapsto (ax \bmod 2^k) \mbox{ div } 2^{k-l}\ |\ a \in \{0,\ldots,2^k-1\} \wedge a \mbox{ nieparzyste} \}.
    $$
    Operacja \(\mbox{div }2^{k-l}\) bierze \(l\) pierwszych (najbardziej znaczących) bitów. Implementacja funkcji z powyższej rodziny jest bardzo łatwa, gdy liczby typu int są z przedziału \(\{0,\ldots,2^k-1\}\). Odpowiedni kod w języku C wyglądałby następująco:

    int function h(int x) {
      return (a*x) >> (k-l);
    }

    Funkcje te są często stosowane w praktyce, ponieważ obliczanie ich wartości jest bardzo szybkie (znaczenie szybsze niż w przypadku funkcji liniowych opisanych wcześniej, choć również istotnie wolniejsze niż w przypadku funkcji z rodziny tabulacyjnej), natomiast same funkcje są bardzo proste w implementacji.

    Twierdzenie 8
    Rodzina \(\mathcal{H}_{k,l}\) jest \((2,2)\)-uniwersalna.

    Dowód
    Niech \(x,y \in \{0,\ldots,2^k-1\}\). Załóżmy, że \(x > y\) i niech \(h_a\) będzie funkcją wybraną losowo z \(\mathcal{H}_{k,l}\). Chcemy pokazać, że
    \[
    \mathbb{P}[h_a(x) = h_a(y)] \leq \frac{1}{2^{l-1}}.
    \]
    Policzmy, ile jest takich \(a\), dla których \(h_a(x) = h_a(y)\). Ta równość jest równoważna nierówności
    \[
    | ax \bmod 2^k - ay \bmod 2^k| < 2^{k-l}.
    \]
    Niech \(z = x - y\), wtedy powyższą nierówność możemy zapisać jako (#)
    \[
    | az \bmod 2^k | < 2^{k-l}.
    \]
    Z założenia \(z \not\equiv 0 \pmod{2^k}\) oraz \(a\) jest nieparzyste, zatem (##)
    \[
    az \not\equiv 0 \pmod {2^k}
    \]
    Warunki (#) i (##) zachodzą, gdy (###)
    \[
    az \bmod 2^k \in \{1,\ldots,2^{k-l}-1\} \cup \{2^k - 2^l+1, \ldots, 2^k-1\}
    \]
    Niech \(z = 2^s\cdot z'\), gdzie \(z'\) jest nieparzyste. Zbiór \(A = \{ 1, 3, 5, \ldots, 2^k-1\}\) jest grupą z mnożeniem modulo \({2^k}\). Wówczas \(\{z'\cdot a\ |\ a \in A\} = A\), a więc ilość liczb \(a \in A\) spełniających (###) jest równa ilości liczb \(a\), dla których
    \[a\cdot 2^s \bmod 2^k\in \{1,\ldots,2^{k-l}-1\} \cup \{2^k - 2^l+1, \ldots, 2^k-1\}.\]
    Pierwszy zbiór jest postaci \(\underbrace{0\ldots 0}_{l \text{ bitów}}\underbrace{\underline{\text{coś}\;\neq 0}}_{k-l\text{ bitów}}\) lub \(\underbrace{1\ldots 1}_{l \text{ bitów}}\underbrace{\underline{\text{coś}\;\neq 0}}_{k-l\text{ bitów}}\). Jeśli \(s \geq k-l\), to końcówka będzie zerowa, więc nie ma takich liczb \(a\). Jeśli \(s < k-l\), to \(a\) zaczyna się od samych \(1\) lub samych \(0\), potem wybieramy \(k-l\) bitów, z których ostatni musi być równy \(1\), zatem \(a\) można wybrać na \(2\cdot 2^{k-l-1} = 2^{k-l}\) sposobów, co daje ostatecznie, że \(\mathbb{P}[h_a(x) = h_a(y)] \leq \frac{2^{k-l}}{2^{k-1}} = \frac{1}{2^{l-1}}=\frac{2}{m}.\) Stąd, rodzina \(\mathcal{H}_{k,l}\) jest \((2,2)\)-uniwersalna. ♦

    Haszowanie 2: haszowanie doskonałe i kukułkowe

    Oczekiwana liczba kolizji w haszowaniu uniwersalnym

    Niech \(S \subseteq U\) będzie ustalonym zbiorem oraz niech \(h : U \rightarrow \{0,\ldots,m-1\}\) będzie rodziną wybraną losowo z pewnej rodziny uniwersalnej. Przypomnijmy, że kolizją nazywamy parę \(x,y \in S\) taką, że \(h(x)=h(y)\). Oczywiście im większe wybierzemy \(m\), tym (średnio) kolizji będzie mniej. Spróbujmy ustalić tę zależność.

    \[\mathbb{E}[\text{całkowita liczba kolizji}] = \mathbb{E}[\sum_{x,y \in S} \mathbf{1}_{h(x) = h(y)}] =\sum_{x,y \in S} \mathbb{E}[\mathbf{1}_{h(x) = h(y)}] = \sum_{x,y \in S} \mathbb{P}[h(x) = h(y)] \leq {n\choose 2} \frac{1}m < \frac{n^2}{2m}
    \]

    Z nierówności Markowa otrzymujemy:
    \[\mathbb{P}[\text{całkowita liczba kolizji} \geq \frac{n^2}m] < \frac{1}2 .\]

    Wniosek 1
    Dla \(m=n^2\), z prawdopodobieństwem co najmniej \(\frac{1}{2}\), w ogóle nie będzie kolizji .

    Wniosek 2
    Dla \(m=n\), z prawdopodobieństwem co najmniej \(\frac{1}{2}\), całkowita liczba kolizji będzie mniejsza \(n\).

    Statyczny słownik w pamięci \(O(n^2)\)

    W wielu zastosowaniach korzystamy ze słownika w następujący sposób: najpierw budujemy słownik dla danego zbioru \(S\), a następnie słownik nie jest już modyfikowany, chcemy jedynie wyszukiwać w nim elementy. Prowadzi to do problemu statycznego słownika, w którym chcemy zaimplementować dwie operacje:

    • Build(\(S\)) -- buduje strukturę danych reprezentującą zbiór \(S\),
    • Lookup(\(x\)) -- czy \(x \in S\)?

    Niech \(|S|=n\). Na podstawie wniosku 1, jeśli dysponujemy pamięcią \(O(n^2)\), możemy zbudować bardzo efektywny statyczny słownik. Mianowicie, przyjmujemy \(m = n^2\). Operacja Build wygląda nastepująco. Inicjalizujemy tablicę haszującą \(T[0,\ldots,m-1]\) wartościami NULL. Losujemy funkcję haszującą \(h\) z rodziny uniwersalnej. Następnie, dla każdego \(x\in S\), jeśli \(T[h(x)] = NULL\) to wpisujemy \(T[h(x)] := x\), w przeciwnym przypadku dostaliśmy kolizję: usuwamy z tablicy \(T\) wstawione elementy i próbujemy ponownie (losujemy \(h\), wstawiamy elementy \(S\) do \(T\)), aż do skutku. W efekcie otrzymujemy funkcję haszującą \(h\), która nie ma żadnych kolizji na zbiorze \(S\), czyli Lookup(\(x\)) działa w czasie pesymistycznym stałym: po prostu sprawdza, czy \(T[h(x)]=x\).

    Pozostaje odpowiedzieć na pytanie, w jakim czasie działa Build. Każda faza operacji Build (wylosowanie \(h\) i sprawdzenie czy są kolizje) zajmuje czas pesymistyczny \(O(n)\). Jaka jest oczekiwana liczba faz? Jest to zmienna losowa o rozkładzie geometrycznym z prawdopodobieństwem sukcesu \(p \ge \frac{1}{2}\), gdzie oszacowanie \(p\) wynika z punktu wniosku 1. Wartość oczekiwana takiej zmiennej wynosi \(\frac{1}{p} \le 2 \). Stąd, średnio wykonają się co najwyżej 2 fazy, więc oczekiwany czas operacji Build jest rzędu \(O(n)\), jeśli nie liczyć inicjalizacji, która niestety podnosi całkowity (oczekiwany) czas do \(O(n^2)\).

    Opisana struktura danych składa się z dwóch elementów: tablicy \(T\), która zajmuje pamięć rzędu \(O(n^2)\) oraz reprezentacji funkcji haszującej \(h\), która zajmuje pamięć stałą jeśli korzystamy z jednej z rodzin uniwersalnych opisanych w poprzednim wykładzie.

    Wniosek 3
    Można zaimplementować statyczny słownik w pamięci \(O(n^2)\) tak, że Build działa w oczekiwanym czasie \(O(n^2)\), natomiast operacja Lookup jest wykonywana w pesymistycznym czasie stałym.

    Haszowanie doskonałe

    W 1984 roku Fredman, Komlos i Szemeredi pokazali, że dla statycznego słownika pesymistyczny czas stały zapytań można uzyskać nawet przy liniowej pamięci. Jedyną ceną będzie randomizacja, czas budowy struktury jest liniowy, ale w sensie wartości oczekiwanej. W tym rozdziale opisujemy ten piękny rezultat, zwany haszowaniem doskonałym.

    Niech \(\mathcal{H}\) będzie uniwersalną rodziną funkcji haszujących postaci \(h:U \rightarrow \{0,\ldots,n-1\}\). Pseudokod operacji Build można przedstawić następująco:

    1. Losuj \(h\) z rodziny \(\mathcal{H}\) dopóki całkowita liczba kolizji w zbiorze \(S\) jest mniejsza niż \(n\).
    2. Dla każdego \(i=0,\ldots,n-1\):
      1. Niech \(S_i=\{x\in S\ |\ h(x)=i\}\).
      2. Zbuduj statyczny słownik \(T_i\) dla zbioru \(S_i\) algorytmem z poprzedniego rozdziału. Adres słownika \(T_i\) przechowujemy w komórce \(T[i]\) tablicy \(T\) o długości \(n\).

    Operacja Lookup(\(x\)) sprowadza się do wykonania Lookup(\(x\)) w słowniku \(T_{h(x)}\), a więc zadziała w pesymistycznym czasie stałym.

    Na rozmiar struktury składa się tablica \(T\) rozmiaru \(O(n)\), reprezentacja funkcji haszującej \(h\) rozmiaru \(O(1)\) oraz słowniki \(T_i\) o łącznym rozmiarze \(O(\sum_{i=0}^{n-1} |S_i|^2)\). Tę wielkość można oszacować następująco:
    \[
    \sum_{i=0}^{n-1} |S_i|^2 = 2\sum_{i=0}^{n-1} {|S_i| \choose 2} + n \leq 3n,
    \]
    ponieważ \(\sum_{i=0}^{n-1} {|S_i| \choose 2} \) jest całkowitą liczbą kolizji elementów z \(S\) przy użyciu funkcji \(h\), a pamiętamy, że \(h\) została wybrana w taki sposób, że liczba kolizji nie przekracza \(n\). Całkowity rozmiar jest zatem rzędu \(O(n)\).

    Podobnie jak w poprzednim rozdziale, liczba losowań w punkcie 1 algorytmu ma rozkład geometryczny z prawdopodobieństwem sukcesu co najmniej 1/2 (tym razem korzystamy z wniosku 2), a więc oczekiwana liczba prób nie przekracza 2. Ponieważ sprawdzenie liczby kolizji można wykonać w czasie \(O(n)\), więc punkt 1. wykona się w oczekiwanym czasie \(O(n)\).

    Ponieważ dla każdego \(i\) słownik \(T_i\) jest budowany w oczekiwanym czasie \(O(|S_i|^2)\) więc z liniowości wartości oczekiwanej, punkt 2. wykona się w oczekiwanym czasie \(O(\sum_{i=0}^{n-1} |S_i|^2)\), a tę wielkość oszacowaliśmy już przez \(O(n)\).

    Możemy podsumować nasze rozważania w formie twierdzenia.

    Twierdzenie (Fredman, Komlos, Szemeredi)
    Dla danego \(n\)-elementowego zbioru \(S\) można w oczekiwanym czasie \(O(n)\) zbudować statyczny słownik o rozmiarze \(O(n)\), w którym wyszukiwanie elementu zajmuje pesymistyczny czas stały. ♦

    Haszowanie kukułkowe

    Tym razem rozwiązujemy pełny problem słownika, czyli będziemy implementować wszystkie trzy operacje -- Lookup, Insert i Delete. Celem jest słownik, w którym operacja Lookup (w wielu zastosowaniach wykonywana znacznie częściej niż Insert i Delete) działa w pesymistycznym czasie stałym, natomiast Insert w oczekiwanym czasie stałym. Przedstawimy ciekawe rozwiązanie tego problemu, nazywane haszowaniem kukułkowym, zaproponowane w 2001r przez Pagha i Rodlera. Inspiracją dla tej metody było ciekawe zjawisko siły podwójnego wyboru znane w teorii schematów urnowych. Jeśli wrzucimy losowo \(n\) kul do \(n\) urn, to z prawdopodobieństwem co najmniej \(1-\frac{1}{n}\) najbardziej zapełniona urna będzie miała \(\Theta(\log n / \log\log n)\) kul. Wyobraźmy sobie jednak nieco zmodyfikowany proces: dla każdej kuli wybieramy losowo dwie urny i kulę umieszczamy w mniej zapełnionej urnie. Wówczas można udowodnić, że z prawdopodobieństwem co najmniej \(1-\frac{1}{n}\) najbardziej zapełniona urna będzie miała \(\Theta(\log\log n)\) kul. Widzimy, że wprowadzenie wyboru między dwiema lokacjami dla kuli spowodowało wykładniczą poprawę.

    W haszowaniu kukułkowym używamy następującej analogii z powyższym schematem urnowym: każdy element \(x\in U\) ma dwa miejsca w strukturze danych, w których może się znajdować. Dokładniej, używamy dwóch funkcji haszujących \(h_1,h_2: U \rightarrow \{0,\ldots,m-1\}\) oraz dwóch tablic \(T_1,T_2[0, \ldots, m-1]\), dla pewnego \(m\ge 2n\). Chwilowo, dla uproszczenia załóżmy, że \(h_1\) i \(h_2\) zostały wybrane losowo z rodziny \(n\)-niezależnej (pod koniec wykładu osłabimy to założenie). W komórkach tablic \(T_1\) i \(T_2\) przechowujemy pojedyncze klucze (elementy \(S\)). W trakcie działania algorytmu będzie zachodził następujący niezmiennik:
    $$
    x \in S \iff T_1[h_1(x)] =x \vee T_2[h_2(x)] = x.
    $$
    Implementacja operacji Lookup jest w tym momencie jasna. Podobnie Delete(\(x\)), usuwa \(x\) z \(T_1[h_1(x)]\) lub \(T_2[h_2(x)]\). Jest oczywiste, że obie te operacje działają w pesymistycznym czasie stałym. W dalszej części zajmujemy się operacją Insert.

    procedure Insert (x)
      if (T2 [h1[x]] = x) or (T2 [h2[x]] = x) then Exit;
     
      for i := 1 to MAX_LOOP do
        x :=: T1[h1[x]];  {zamiana wartości x i T1[h1[x]]}
        if x = NULL then Exit; 
        x :=: T2[h2[x]];  {zamiana wartości x i T2[h2[x]]}
        if x = NULL then Exit; 
      end;
     
      rehash (x);
    end


    Operacja :=: zamienia wartości zmiennych, tzn. x:=:y odpowiada trzem operacjom: a:=x; x:=y; y:=a;. Przykład działania operacji Insert(\(x\)) możemy prześledzić na rysunku obok. Chcemy wstawić \(x\) w \(T_1[h_1(x)]\). Niestety jest tam już element 5. Wówczas podmieniamy 5 na \(x\) w komórce \(T_1[h_1(x)]\), podobnie jak kukułka podmienia w gnieździe innego ptaka jedno z jego jajek na swoje jajko. Kukułka nie przejmuje się wyrzuconym jajkiem, my jednak próbujemy wstawić 5 w inne miejsce struktury danych. Alternatywną lokacją jest \(T_2[h_2(5)]\). Umieszczamy tam 5, ale wcześniej była tam 3, więc teraz wstawiamy 3. Alternatywną lokacją jest \(T_1[h_1(3)]\), gdzie mamy element 7. Z kolei 7 umieszczamy w \(T_2[h_2(7)]\), usuwając stamtąd 2. Wreszcie, okazuje się, że alternatywna lokacja dla 2 jest wolna: możemy wstawić 2 w \(T_1[h_1(2)]\) i zakończyć wykonanie procedury.

    Podczas wstawiania elementu algorytm ,,wędruje'' po strukturze danych wzdłuż pewnej ścieżki (tak naprawdę marszruty, gdyż może powtórnie odwiedzić tę samą komórkę tablicy). W dalszej części wykładu pokażemy, że wartość oczekiwana długości tej marszruty jest niewielka (stała). Z pewnym małym prawdopodobieństwem może się jednak zdarzyć tak, że algorytm ulegnie zapętleniu: będzie odwiedzał klucze z pewnego zbioru \(A\subseteq S\), podczas gdy liczba możliwych miejsc, w których te elementy mogą się znajdować będzie zbyt mała, tzn. \(|h_1(A)| + |h_2(A)| < |A|\). Jeśli wykonanych zostanie zbyt wiele kroków (więcej od MAX_LOOP -- pokażemy później, że wystarczy przyjąć MAX_LOOP\(=O(\log n)\), a na razie załóżmy, że MAX_LOOP\(\le n\)), funkcja Insert poddaje się, tzn. uznaje, że najprawdopodobniej nastąpiło zapętlenie. W takiej sytuacji wykonywana jest operacja rehash\((x)\), która działa następująco. Tablice \(T_1\) i \(T_2\) są czyszczone, losowane są nowe funkcje \(h_1\) i \(h_2\) i przy ich pomocy wszystkie elementy \(S\cup\{x\}\) wstawiane są na nowo do tablic za pomocą algorytmu Insert. Oczywiście nawet po wylosowaniu nowych \(h_1\) i \(h_2\) z pewnym prawdopodobieństwem jedno ze wstawień może się poddać -- wtedy ponownie losowane są nowe funkcje \(h_1\) i \(h_2\) i tak aż do skutku. W takiej sytuacji pojedyncze wykonanie Insert będzie bardzo kosztowne: jedna próba wylosowania nowych funkcji haszujących i rozmieszczenia zbioru \(S\) w \(T_1\) i \(T_2\) zgodnie z tymi funkcjami zajmuje czas \(\Omega(n)\). Okaże się jednak, że taka sytuacja zdarza się tak rzadko, że całkowity oczekiwany czas Insert i tak jest \(O(1)\).

    Oszacowanie oczekiwanego czasu działania Insert

    Niech \(G\) będzie grafem dwudzielnym, którego wierzchołkami są komórki tablic \(T_1,T_2\), oraz dla każdego \(x\in S\) graf \(G\) zawiera krawędź \(h_1(x)h_2(x)\). Zauważmy, że działanie funkcji Insert wyznacza pewną marszrutę w tym grafie. Niech \(x_1,\ldots,x_k\) będą kolejnymi kluczami, które ,,odwiedza'' ta marszruta. Marszruta ta może zawierać cykle, nie może być jednak zupełnie dowolna.

    Mianowicie, zobaczmy co się dzieje gdy marszruta po raz pierwszy powraca do wierzchołka, w którym już była, tzn. pewien klucz \(x_i\) jest wstawiany w miejsce klucza \(x_j\), dla pewnego \(j< i\). Wówczas \(x_j\) jest wstawiany w miejsce \(x_{j-1}\), \(x_{j-1}\) w miejsce \(x_{j-2}\) itd, czyli cofamy się wzdłuż marszruty aż do komórki \(T_1[h_1(x_1)]\). Następnie odwiedzana jest komórka \(T_2[h_2(x_1)]\). Od tej chwili marszruta ponownie może odwiedzać nowe klucze. Jeśli w pewnej chwili natrafi na pustą komórkę, operacja Insert się zakończy. W takiej sytuacji powiemy, że mamy do czynienia z pojedynczym cyklem (patrz rysunek 1b). Jeśli natomiast marszruta ponownie natrafi na wcześniej odwiedzoną komórkę \(x_l\), to tak generowana marszruta bez końca będzie już poruszać się po krawędziach odpowiadających odwiedzonym kluczom (a dokładniej poruszałaby się bez końca, gdyby nie sztywne ograniczenie 2MAX_LOOP na liczbę wykonanych kroków, patrz rysunek c). Taką marszrutę nazwiemy podwójnym cyklem.

    Rysunek 1: Trzy możliwe sytuacje podczas wstawiania: a) ścieżka, b) pojedynczy cykl, c) podwójny cykl

    Lemat 4
    Prawdopodobieństwo, że marszruta nie jest podwójnym cyklem oraz jej długość wynosi co najmniej \(k\) nie przekracza \(\left( \frac{1}2\right)^{k/3-2}\).

    Dowód
    Niech \(A\) oznacza zdarzenie, którego prawdopodobieństow chcemy oszacować.
    Zdarzenie \(A\) implikuje, że zaszła jedna z dwóch sytuacji:

    • w ogóle nie było cyklu; każdy wierzchołek był odwiedzany raz
    • był tylko pojedynczy cykl

    Jeśli dana marszruta ma długość \(k\), to istnieje podmarszruta o początku w \(T_1[h_1(x_1)]\) lub \(T_2[h_2(x_1)]\) o długości \(k/3\), która jest ścieżką. Oznaczmy przez \(x'_1,\ldots,x'_{k/3}\) jej kolejne klucze (jest to spójny podciąg ciągu \(x_1,\ldots,x_k\)). Oszacujemy prawdopodobieństwo zdarzenia, że taka marszruta faktycznie pojawia się w grafie losowym stworzonym na podstawie \(h_1\) i \(h_2\):

    \[
    \begin{align}
    & \mathbb{P}[A] \le \\
    & 2\cdot\mathbb{P}[\text{istnieje $k/3$ parami różnych }x_2,\ldots,x_{k/3} \text{ t.ż.} h_1(x_1) = h_1(x_2) \wedge h_2(x_2)=h_2(x_3) \wedge h_1(x_3) = h_1(x_4) \wedge \ldots] \le\\
    & 2 \cdot n^{k/3-1} \cdot \left( \frac{1}m\right)^{k/3-1} = 2 \cdot \left( \frac{n}m\right)^{k/3-1} \leq \left( \frac{1}2\right)^{k/3-2}.
    \end{align}
    \]

    Pokażemy teraz, że prawdopodobieństwo wystąpienia niekorzystnej sytuacji podwójnego cyklu jest bardzo małe.

    Lemat 5
    Prawdopodobieństwo, że wygenerowana przy operacji Insert marszruta jest podwójnym cyklem nie przekracza \(O(1/n^2)\).

    Dowód
    Niech \(x_i\), \(x_j\) i \(x_l\) będą takie jak w definicji podwójnego cyklu powyżej.

    Dla ustalonego klucza początkowego \(x_1\), oszacujmy liczbę możliwych cykli podwójnych odwiedzających \(k\) kluczy:

    • na co najwyżej \(n^{k-1}\) sposobów wybieramy pozostałe klucze
    • na \(k^3\) sposobów wybieramy kształt cyklu (czyli indeksy \(i,j,l\))
    • na \(m^{k-1}\) sposobów wybieramy wierzchołki grafu, na których będzie leżał cykl (\(k-1\), bo dla jednego klucza nie ma miejsca)

    Razem co najwyżej \(n^{k-1} \cdot k^3 \cdot m^{k-1}\) cykli.

    Każdy taki cykl składa się z \(k\) krawędzi, z których każda pojawia się niezależnie z prawdopodobieństwem \(\left(\frac{1}{m}\right)^2\), na mocy niezależności losowania \(h_1\) i \(h_2\) oraz faktu, że \(h_1\) i \(h_2\) mają rozkład jednostajny. Z \(n\)-niezależności rodziny funkcji haszujących, z których wybierane są \(h_1\) i \(h_2\), mamy, że prawdopodobieństwo pojawienia się takiego cyklu nie przekracza \(\left(\frac{1}{m}\right)^{2k}\) (zauważmy, że potrzebowaliśmy jedynie \(k\)-niezależności, a \(k\le 2\)MAX_LOOP. Stąd

    \[\mathbb{P}[\text{marszruta jest cyklem podwójnym o $k$ kluczach}]\leq n^{k-1} \cdot k^3 \cdot m^{k-1} \cdot \frac{1}{m^{2k}}.\]

    Tak więc
    \[\mathbb{P}[\text{marszruta jest cyklem podówójnym}] \leq \sum_{k \geq 3} \frac{n^{k-1} \cdot k^3 \cdot m^{k-1}}{m^{2k}} \leq \sum_{k \geq 3} k^3 \cdot \frac{n^{k-1}}{m^{k+1}} \leq \frac{1}{m^2}\sum_{k \geq 3} k^3 \cdot \frac{1}{2^{k-1}} =O\left(\frac{1}{m^2}\right) = O\left(\frac{1}{n^2}\right)\]

    Widzimy, że operacja wstawienia poddała się, gdy wystąpiła jedno ze zdarzeń:

    • był podwójny cykl -- z lematu 6 prawdopodobieństwem \(O(1/{n^2})\)
    • była długa marszruta -- z lematu 4 z prawdopodobieństwem \(O\left(\left(\frac{1}{2}\right)^{\frac{MaxLoop}{3}}\right)\),

    Ponieważ przyjęliśmy MAX_LOOP\( = 6 \lg n\), otrzymujemy następujący wniosek.

    Wniosek 6
    Prawdopodobieństwo, że operacja wstawienia podda się nie przekracza \(O(1/{n^2})\). ♦

    Twierdzenie
    Oczekiwany czas operacji Insert jest ograniczony przez stałą.

    Dowód
    Obliczmy oczekiwany czas operacji rehash. Wykonuje ona \(n\) wstawień. Wówczas, na mocy wniosku 7,
    \(\mathbb{P}[\text{pewne wstawienia się poddało}] \le n \mathbb{P}[\text{konkretne wstawienie się poddało}] = O(1/n)\).
    Zatem liczba powtórzeń zanim wszystkie operacje Insert się powiodą ma rozkład geometryczny z prawdopodobieństwem sukcesu \(1-O(1/n)\), stąd jej wartość oczekiwana to \((1-O(1/n))^{-1} = 1+O(1/n)\). Na jedną taką próbę składa się wylosowanie funkcji haszujących \(h_1\) i \(h_2\) oraz wstawienie co najwyżej \(n\) kluczy. Każde wstawienie zajmuje czas \(O(\mathtt{MAX\_LOOP})=O(\log n)\). Stąd, pesymistyczny czas pojedynczej próby będzie \(O(n\log n)\) przy założeniu, że losowanie funkcji nie trwa zbyt długo. Ponieważ oczekiwaną liczbę prób oszacowaliśmy przez \(1+O(1/n)\), więc ostatecznie oczekiwany czas rehash (czyli wszystkich prób) nie przekracza \(O(n \log n)\).
    Stąd,

    \[
    \mathbb{E}[\text{czas Insert $+$ czas rehash | Insert poddał się}] = O(\log n)
    \]

    Teraz zajmijmy się przypadkiem, gdy Insert nie poddał się.

    Zauważmy, że

    \[
    \mathbb{P}[\text{marszruta ma długość $\ge k$ | Insert nie poddał się}] = \frac{\mathbb{P}[\text{marszruta ma długość $\ge k$ i Insert nie poddał się}]}{\mathbb{P}[\text{Insert nie poddał się}]}
    \]

    Ponieważ jeśli insert nie poddał się, to nie było podwójnego cyklu oraz z Lematu 4 i Wniosku 6 mamy

    \[
    \mathbb{P}[\text{marszruta ma długość $\ge k$ | Insert nie poddał się}] \le \mathbb{P}[\text{marszruta ma długość $\ge k$ i nie nie ma podwójnego cyklu}] \le \frac{(\frac{1}2)^{k/3-2}}{1-O(n^{-2})}
    \]

    Stąd,

    \[
    \begin{align*}
    &\mathbb{E}[\text{czas Insert | Insert nie poddał się}] = \sum_k \mathbb{P}[\text{marszruta ma długość $\ge k$ | Insert nie poddał się}] = O (\sum_k (\frac{1}2)^{k/3-2}) = O(1).
    \end{align*}
    \]

    Razem otrzymujemy

    \[
    \begin{align*}
    &\mathbb{E}[\text{czas Insert}] \\
    ={}&\mathbb{E}[\text{czas Insert | Insert nie poddał się}]\cdot \mathbb{P}[\mbox{Insert nie poddał się}] +\mathbb{E}[\text{czas Insert $+$ czas rehash | Insert poddał się}]\cdot \mathbb{P}[\text{Insert poddał się}] \\
    ={}& O(1) (1-O(1/n^2))+ O(n \log n)O(1/n^2) = O(1).
    \end{align*}
    \]

    Zauważmy na koniec, że powyższa analiza jest poprawna również wówczas, gdy funkcje \(h_1\) i \(h_2\) są losowane z rodziny 2MAX_LOOP-niezależnej, bo rozpatrujemy tylko ścieżki maksymalnie tej długości, a pamiętamy, że MAX_LOOP jest rzędu \(O(\log n)\). Istnieją rodziny funkcji haszujących które oferują taką niezależność, zachowując równocześnie stały czas obliczania wartości funkcji, a także czas losowania i rozmiar rzędu \(O(n)\), jednakże mają one znaczenie tylko teoretyczne. Z drugiej strony, haszowanie kukułkowe w praktyce zachowuje się dobrze dla rodzin funkcji haszujących o dużo słabszych własnościach. Jest problem otwartym, czy oczekiwany czas operacji wstawiania pozostanie stały jeśli zastosujemy rodzinę funkcji tabulacyjnych opisane w poprzednim wykładzie (jak pamiętamy nie jest ona nawet 4-niezależna, taki wynik wymagałby więc zupełnie innej analizy niż przeprowadzona powyżej).

    Haszowanie 3: haszowanie liniowe

    Algorytmy operacji słownikowych w haszowaniu liniowym

    W tym rozdziale przedstawimy kolejne bardzo naturalne rozwiązanie problemu dynamicznego słownika oparte na haszowaniu. Liczby całkowite ze zbioru \(S\) reprezentowanego przez nasz słownik będą przechowywane bezpośrednio w tablicy \(T[0..m - 1]\). Rozmiar \(T\) będzie większy niż \(n=|S|\), choć liniowy względem \(n\). Zwykle w zastosowaniach przyjmuje się \(m=2n\), do udowodnienia asymptotycznych oszacowań na czasy działania operacji wystarczy \(m=(1+\varepsilon)\cdot n\), natomiast my dla ustalenia uwagi przyjmiemy \(m=3n\). Algorytmy operacji słownikowych będą korzystały z pojedynczej funkcji haszującej \(h:U\rightarrow \{0..m-1\}\).

    Algorytmy poszczególnych operacji są następujące:

    Poprawność opisanych algorytmów powinna być dość jasna (w zasadzie tylko w przypadku delete jest się nad czym zastanawiać).

    Zauważmy, że opisane operacje (szczególnie lookup i insert) operują z reguły na spójnym bloku pamięci (chyba że, pechowo, algorytm wyszukiwania musi ,,przeskoczyć'' z końca tablicy na początek). Taki sposób dostępu do pamięci jest kluczowy dla praktycznej efektywności algorytmów, gdyż w komputerach o współczesnej architekturze z reguły wykonany zostanie odczyt pojedynczego bloku pamięci przy pierwszej operacji dostępu do tablicy T, natomiast wartości kolejnych komórek zostaną już odczytane z szybkiej pamięci typu cache. Z tego względu haszowanie liniowe jest rozwiązaniem najczęściej wybieranym w praktyce. W tym wykładzie dowiemy się, że odpowiednio wybierając funkcję haszującą możemy zagwarantować także teoretyczną efektywność wszystkich operacji słownikowych.

    Analiza oczekiwanej złożoności czasowej operacji

    Pamiętamy z wcześniejszego wykładu, że przy haszowaniu przez łańcuchowanie, jeśli \(h\) jest wybrana losowo z rodziny uniwersalnej, to oczekiwane czasy operacji słownikowych są stałe. Od czasu uzyskania tego wyniku przez Cartera i Wegmana w 1977r. pozostawało otwartym pytanie, czy można uzyskać analogiczny wynik dla haszowania liniowego, używając rodziny uniwersalnej lub jakiejś innej rodziny funkcji haszujących. Dopiero w 2007 odpowiedzi udzielili Rasmus Pagh, Anna Pagh i Milan Ruzic.

    Twierdzenie 1
    Jeśli w haszowaniu liniowym \(h\) jest wybrana losowo z uniwersalnej rodziny funkcji haszujących Cartera i Wegmana (patrz Przykład 2 w I wykładzie o haszowaniu), to istnieje \(S\subset U\) taki, że całkowity oczekiwany czas wstawienia wszystkich elementów z \(S\) wynosi \(\Omega(n\log n)\).

    Równocześnie pokazali oni także następujący wynik pozytywny:

    Twierdzenie 2
    Jeśli w haszowaniu liniowym \(h\) jest wybrana losowo z dowolnej 5-niezależnej rodziny funkcji haszujących, to oczekiwany czas operacji słownikowych jest stały, a dokładniej wynosi \(O((1-\alpha)^{-7/6})\), gdzie \(\alpha=n/m\).

    Zacznijmy od tego, że wynik \(O((1-\alpha)^{-7/6})\) jest naprawdę dobry, gdyż \(O((1-\alpha)^{-1})\) to oczekiwany czas jaki dostajemy, gdy zamiast szukać wolnego miejsca na \(x\) używając kolejnych pozycji w tablicy \(T\), korzystamy z w pełni losowej permutacji pozycji (patrz np. podręcznik Cormena i innych), co jest bardzo wyidealizowanym założeniem. Twierdzenie 2 jest dość zaskakujące, gdyż w świetle twierdzenia 1, które ,,z grubsza'' mówi, że 2-niezależność nie wystarcza, mogłoby się wydawać, że również \(O(1)\)-niezależność nie jest wystarczająca. Dodatkowo dziwi tu liczba 5, na pierwszy rzut oka wygląda to na niedokładne szacowanie, nieoptymalny wynik. Nic bardziej błędnego, gdyż w 2010r. Patrascu i Thorup wykazali, że

    Twierdzenie 3
    Istnieje \(4\)-niezależna rodzina funkcji haszujących \(\mathcal{H}\) taka, że jeśli w haszowaniu liniowym \(h\) jest wybrana losowo z \(\mathcal{H}\), to istnieje \(S\subset U\) taki, że całkowity oczekiwany czas wstawienia wszystkich elementów z \(S\) wynosi \(\Omega(n\log n)\).

    W dalszej części udowodnimy uproszczoną wersję twierdzenia 2.

    Twierdzenie 4
    Jeśli w haszowaniu liniowym \(h\) jest wybrana losowo z dowolnej 5-niezależnej rodziny funkcji haszujących \(\mathcal{H}\), oraz \(m\ge 3n\), to oczekiwany czas operacji słownikowych jest stały.

    Zauważmy, że przyjęliśmy \(\alpha \le \frac{1}{3}\). Spójną składową tablicy \(T\) będziemy nazywać dowolny maksymalny ciąg kolejnych niepustych komórek tablicy \(T\). Niech \(cc(p)\) oznacza spójną składową zawierającą komórkę \(p\), natomiast \(|cc(p)|\) jej długość. Łatwo widać, że prawdziwy jest następujący lemat.

    Lemat 5
    Czas działania każdej z operacji insert(\(x\)), lookup(\(x\)) oraz delete(\(x\)) można oszacować przez \(O(|cc(h(x))|)\). ♦

    Weźmy dowolne \(x\in U\). Zgodnie z lematem 5, aby wykazać twierdzenie 4 wystarczy, że oszacujemy przez pewną stałą oczekiwaną długość spójnej składowej zawierającej pozycję \(h(x)\). Oczywiście zawsze istnieje takie \(k\), że \(|cc(h(x))| \in \{2^k, \ldots, 2^{k+1}-1\}\). Podzielmy komórki \(T\) na równe bloki długości \(2^{k-2}\).

    Przypomnijmy, że notacja \(\mathbf{1}_{\mathcal{E}}\), gdzie \(\mathcal{E}\) jest dowolnym zdarzeniem oznacza indykator zdarzenia \(\mathcal{E}\), czyli zmienną losową która przyjmuje wartość 1 gdy \(\mathcal{E}\) zaszło i 0 w przeciwnym przypadku (formalnie, \(\mathbf{1}_{\mathcal{E}}(\omega)=[\omega \in \mathcal{E}]\)).

    Lemat 6
    \(\mathbb{E}[|h(S)\cap B|] = \alpha|B| = \frac{1}{3} |B|\)

    Dowód
    Korzystamy z liniowości wartości oczekiwanej i jednostajności \(\mathcal{H}\), czyli faktu, że \(\mathbb{P}[h(e)=b] = 1/m\) dla dowolnych \(e\in U\), \(b\in [m]\). Zauważmy, że \(\mathbb{E}[|h(S)\cap B|] = \sum_{e \in S} \mathbb{E}[\mathbf{1}_{h(e)\in B}] = \sum_{e \in S} \mathbb{P}[h(e)\in B] = \sum_{e \in S}\sum_{b \in B}\mathbb{P}[h(e)=b] = n\cdot|B|\cdot \frac{1}{m}= \alpha|B| = \frac{1}{3} |B|\). ♦

    Powiemy, że blok \(B=\{i, i+1, \ldots, i + 2^{k-2} - 1\}\) jest niebezpieczny, gdy \(|h(S) \cap B| \ge \frac{2}{3} |B|\). Uwaga! Zauważmy, że nie liczymy tu ile komórek \(B\) jest zajętych, a jedynie do ilu komórek \(B\) haszują się elementy \(S\).

    Lemat 7
    Jeśli \(h(x)\) jest w spójnej składowej długości \(\in \{2^k, \ldots, 2^{k+1}-1\}\) to jeden z \(O(1)\) bloków przecinających \(cc(h(x))\) jest niebezpieczny.

    Dowód
    Oznaczmy kolejne bloki przecinające \(cc(h(x))\) przez \(B_1, \ldots, B_k\). Zauważmy, że \(4 \le k \le 9\). Załóżmy przeciwnie, że wszystkie te bloki są bezpieczne. W szczególności oznacza to, że mniej niż \(\frac{2}{3}2^{k-2}\) elementów haszujących do \(B_1\) znajduje się w kolejnych blokach. W blokach \(B_2\) i \(B_3\) jest sumarycznie więcej niż \(2\cdot \frac{1}{3}|B|\) komórek, które nie zawierają elementów haszujących do \(B_2\) i \(B_3\). To oznacza, że nie wszystkie z tych komórek zostaną zajęte przez elementy haszujące do \(B_1\), a więc co najmniej jedna komórka pozostanie pusta (gdyż inne elementy nie mogą tam się pojawić). W takim razie \(k\le 3\), sprzeczność. ♦

    Załóżmy teraz, że znamy wartość \(\rho=h(x)\) i chcemy oszacować prawdopodobieństwo, że \(|cc(\rho)| \in \{2^k,\ldots,2^{k+1}-1\}\).
    Ponieważ \(cc(\rho)\) przecina co najwyżej 9 bloków, to jest co najwyżej 17 bloków \(B_1,\ldots, B_k\), które potencjalnie mogą przecinać się z \(cc(\rho)\). Z lematu 7 wiemy, że jeśli \(|cc(\rho)| \in \{2^k,\ldots,2^{k+1}-1\}\) to jeden z tych bloków jest niebezpieczny.
    Stąd, \[\mathbb{P}[|cc(\rho)| \in \{2^k,\ldots,2^{k+1}-1\} | h(x)=\rho] \le \sum_{i=1}^k \mathbb{P}[|h(S)\cap B_i|\ge \frac{2}{3}|B_i| | h(x)=\rho].\]
    Z symetrii i lematu 6 mamy dalej (*)

    \[\mathbb{P}[|cc(\rho)| \in \{2^k,\ldots,2^{k+1}-1\} | h(x)=\rho] = O(1) \cdot \mathbb{P}[|h(S)\cap B|\ge \mathbb{E}(|h(S) \cap B|)+\frac{1}{3}|B| | h(x)=\rho],
    \]
    gdzie \(B\) jest pewnym konkretnym blokiem długości \(2^{k-2}\).

    Od tej pory, dla uproszczenia zakładamy, że wszsytko jest warunkowane przez zdarzenie \(h(x)=\rho\) i będziemy pomijać w prawdopodobieństwach (i wartościach oczekiwanych) napis ,,\(| h(x)=\rho\)''. Zauważmy, że przy tym warunkowaniu, rodzina \(\mathcal{H}\) jest 4-niezależna.

    Niech \(X_e = \mathbf{1}_{h(e) \in B}\) oraz \(X=\sum_{e\in S} X_e\). Przy takiej notacji, chodzi nam o to, żeby oszacować \(\mathbb{P}[X > 2\mathbb{E}(X)]\). Narzuca się, żeby w celu oszacowania powyższego prawdopodobieństwa użyć nierówności Chernoffa, problem polega jednak na tym, że zmienne \(X_e\) nie są niezależne (a jedynie 4-niezależne). Z nierówności Markowa dostajemy szacowanie przez \(\frac{1}{2}\), lecz jak się później okaże jest ono o wiele za słabe. Kolejny pomysł to użycie nierówności Czebyszewa -- dałaby ona lepsze oszacowanie, lecz w dalszym ciągu niewystarczające. Okazuje się, że wystarczy zrobić jeden krok dalej -- mianowicie użyć następującego faktu:

    Nierówność czwartego momentu: \(\mathbb{P}[|X-\mathbb{E}X| > d] \le \frac{\mathbb{E}[(X-\mathbb{E}X)^4]}{d^4}.\)

    Dowód nierówności
    Dowodzimy tak samo jak nierówność Czebyszewa, tylko podnosimy do 4-tej potęgi:
    \[\mathbb{P}[|X-\mathbb{E}X| > d] = \mathbb{P}[(X-\mathbb{E}X)^4 > d^4] \le \frac{\mathbb{E}[(X-\mathbb{E}X)^4]}{d^4},\]
    gdzie ostatnia nierówność wynika z nierówności Markowa.♦

    Pozostaje jedynie oszacować czwarty moment, czyli \(\mathbb{E}[(X-\mathbb{E}X)^4]\). Oznaczmy

    \(Y_e = X_e - \mathbb{E}X_e\) oraz \(Y=\sum_{e\in S} Y_e.\)

    Wówczas \(X-\mathbb{E}X = Y\) i interesuje nas \(\mathbb{E}(Y^4)\). Mamy:
    \[\mathbb{E}[Y^4] = \mathbb{E}\left[\left(\sum_{e\in S}Y_e\right)^4\right] = \sum_{e_1, e_2, e_3, e_4 \in S}\mathbb{E}(Y_{e_1}Y_{e_2}Y_{e_3}Y_{e_4}).\]
    Zauważmy, że zmienne \(Y_e\) są rownież 4-niezależne (gdyż \(X_e\) są takie). Stąd, jeśli w ostatniej sumie \(e_1, \ldots, e_4\) są parami różne, to zmienne \(Y_{e_1}\), \(Y_{e_2}\), \(Y_{e_3}\), \(Y_{e_4}\) są niezależne, czyli \(\mathbb{E}(Y_{e_1}Y_{e_2}Y_{e_3}Y_{e_4}) = \mathbb{E}Y_{e_1}\mathbb{E}Y_{e_2}\mathbb{E}Y_{e_3}\mathbb{E}Y_{e_4}=0\), gdzie ostatnia równość bierze się stąd, że \(\mathbb{E}Y_e = 0\). Ogólniej, jeśli \(e_1 \not \in \{e_2, e_3, e_4\}\) to dwie zmienne \(Y_{e_1}\) i \(Y_{e_2}Y_{e_3}Y_{e_4}\) są niezależne i dostajemy \(\mathbb{E}(Y_{e_1}Y_{e_2}Y_{e_3}Y_{e_4}) = \mathbb{E}Y_{e_1}\mathbb{E}[Y_{e_2}Y_{e_3}Y_{e_4}]=0\). Stąd,
    \[\mathbb{E}[Y^4] = \sum_{e\in S}\mathbb{E}[Y_e^4] + \sum_{e,f\in S; e < f}{4 \choose 2}\mathbb{E}[Y_e^2]\mathbb{E}[Y_f^2].\]

    Dla dowolnego \(e\) i parzystego \(j>0\) mamy
    \[
    \begin{align}
    \mathbb{E}[Y_e^j] &= \left(1-\tfrac{|B|}{m}\right)^j \tfrac{|B|}{m} + \left(-\tfrac{|B|}{m}\right)^j\left(1-\tfrac{|B|}{m}\right) =
    \left(1-\tfrac{|B|}{m}\right)^j \tfrac{|B|}{m} + \left(\tfrac{|B|}{m}\right)^j\left(1-\tfrac{|B|}{m}\right) =\\
    &= \tfrac{|B|}{m} \left(1-\tfrac{|B|}{m}\right) \left(\left(1-\tfrac{|B|}{m}\right)^{j-1} + \left(\tfrac{|B|}{m}\right)^{j-1}\right) < \tfrac{|B|}{m}.
    \end{align}
    \]
    Stąd, pamiętając o oznaczeniu \(\alpha = n/m\),

    \[\mathbb{E}[Y^4] < n \frac{|B|}{m} + 6\frac{n^2}{2}\left(\frac{|B|}{m}\right)= \alpha|B| + 3(\alpha|B|)^2 < 4(\alpha|B|)^2.\]

    Stąd i z nierówności 4-tego momentu mamy, że (**)
    \[\mathbb{P}[X>2\mathbb{E}X] = \mathbb{P}[X-\mathbb{E}X > \mathbb{E}X] < \frac{4(\alpha|B|)^2}{(\alpha|B|)^4} = \frac{4}{\alpha^4}|B|^{-2}=O(|B|^{-2}).
    \]

    Stąd i z (*) mamy

    \[
    \begin{align}
    \mathbb{E}[|cc(\rho)| | h(x)=\rho] &= \sum_l l\cdot \mathbb{P}[|cc(\rho)|=l | h(x)=\rho]\\
    & < \sum_k 2^{k+1}\cdot \mathbb{P}[|cc(\rho)|\in\{2^k,\ldots,2^{k+1}-1\} | h(x)=\rho]\\
    & <^{(*), (**)} \sum_k 2^{k+1}\cdot O(1)\cdot (2^{k-2})^{-2} \\
    & = O(1) \sum_k 2^{-k} \\
    & = O(1).
    \end{align}
    \]

    Ponieważ jest to prawdziwe dla dowolnego \(\rho\in [m]\), więc \(\mathbb{E}[|cc(h(x))|]=O(1)\), a tym samym dowód twierdzenia 4 jest zakończony.

    Literatura

    Wynik przedstawiony w tym wykładzie został po raz pierwszy opisany w pracy

    A. Pagh, R. Pagh, M. Ruzic. Linear probing with constant independence. SIAM J. Comput., 39(3):1107–1120, 2009.

    Użyty tutaj sposób prezentacji dowodów jest jednak w głównej mierze inspirowany blogiem Mihai Pătraşcu oraz pracą

    M. Pătraşcu, M. Thorup. The Power of Simple Tabulation Hashing, Proc. 43rd ACM Symposium on Theory of Computing (STOC 2011).

    Twierdzenie 3 zostało udowodnione w pracy

    M. Patrascu and M. Thorup. On the -independence required by linear probing and minwise independence. Proc. ICALP 2010.