Mam chciwy algorytm, który, jak podejrzewam, może być poprawny, ale nie jestem pewien. Jak sprawdzić, czy jest poprawny? Jakich technik należy użyć do udowodnienia, że chciwy algorytm jest poprawny? Czy istnieją wspólne wzorce lub techniki?
Mam nadzieję, że stanie się to pytaniem referencyjnym, które można wykorzystać do wskazania początkującym; stąd jego szerszy niż zwykle zakres. Prosimy o podanie ogólnych, dydaktycznie przedstawionych odpowiedzi, które są zilustrowane co najmniej jednym przykładem, ale mimo to obejmują wiele sytuacji. Dzięki!
Odpowiedzi:
Ostatecznie będziesz potrzebować matematycznego dowodu poprawności. Poniżej przedstawię kilka technik dowodowych, ale najpierw, zanim się do nich zagłębię, pozwól mi zaoszczędzić trochę czasu: zanim zaczniesz szukać dowodu, wypróbuj losowe testy.
Losowe testy
Jako pierwszy krok zalecamy przetestowanie algorytmu przy użyciu losowych testów. To zadziwiające, jak skuteczne jest to: z mojego doświadczenia wynika, że w przypadku chciwych algorytmów losowe testowanie wydaje się nieuzasadnione. Poświęć 5 minut na kodowanie algorytmu, a możesz zaoszczędzić godzinę lub dwie, próbując znaleźć dowód.
Podstawowa idea jest prosta: zaimplementuj swój algorytm. Zaimplementuj również algorytm referencyjny, o którym wiesz, że jest poprawny (np. Taki, który wyczerpująco wypróbowuje wszystkie możliwości i bierze najlepsze). W porządku, jeśli algorytm referencyjny jest asymptotycznie nieefektywny, ponieważ uruchamiasz go tylko w przypadku małych problemów. Następnie losowo wygeneruj milion małych wystąpień problemów, uruchom oba algorytmy na każdym z nich i sprawdź, czy algorytm kandydujący daje poprawną odpowiedź w każdym przypadku.
Empirycznie, jeśli twój kandydat na chciwy algorytm jest niepoprawny, zazwyczaj odkryjesz go podczas losowych testów. Jeśli wydaje się być poprawny we wszystkich przypadkach testowych, należy przejść do następnego kroku: wymyślenie matematycznego dowodu poprawności.
Matematyczne dowody poprawności
OK, więc musimy udowodnić, że nasz chciwy algorytm jest poprawny: że generuje optymalne rozwiązanie (lub, jeśli istnieje wiele optymalnych rozwiązań, które są równie dobre, że wypisuje jedno z nich).
Podstawowa zasada jest intuicyjna:
Zasada: jeśli nigdy nie dokonasz złego wyboru, zrobisz dobrze.
Chciwe algorytmy zwykle obejmują sekwencję wyborów. Podstawowa strategia dowodowa polega na tym, że spróbujemy udowodnić, że algorytm nigdy nie dokonuje złego wyboru. Chciwe algorytmy nie mogą cofnąć się - gdy dokonają wyboru, są zobowiązane i nigdy nie cofną tego wyboru - dlatego ważne jest, aby nigdy nie dokonali złego wyboru.
Co by się liczyło jako dobry wybór? Jeśli istnieje jedno optymalne rozwiązanie, łatwo jest zobaczyć, co jest dobrym wyborem: każdy wybór identyczny z wyborem optymalnym. Innymi słowy, postaramy się udowodnić, że na dowolnym etapie wykonywania zachłannych algorytmów sekwencja wyborów dokonanych przez algorytm dokładnie odpowiada pewnemu przedrostkowi optymalnego rozwiązania. Jeśli istnieje wiele równie dobrych optymalnych rozwiązań, dobrym wyborem jest takie, które jest zgodne z co najmniej jednym z optymów. Innymi słowy, jeśli sekwencja wyborów algorytmu do tej pory odpowiada przedrostkowi jednego z optymalnych rozwiązań, na razie wszystko jest w porządku (jak dotąd nic się nie stało).
Aby uprościć życie i wyeliminować zakłócenia, skupmy się na przypadku, w którym nie ma żadnych powiązań: istnieje jedno, unikalne, optymalne rozwiązanie. Wszystkie maszyny zostaną przeniesione do skrzyni, w której może istnieć wiele równie dobrych optymów bez żadnych zasadniczych zmian, ale trzeba być bardziej ostrożnym w kwestii szczegółów technicznych. Zacznij od zignorowania tych szczegółów i skupienia się na przypadku, w którym optymalne rozwiązanie jest unikalne; które pomogą Ci skupić się na tym, co najważniejsze.
Istnieje bardzo popularny wzór dowodu, którego używamy. Będziemy ciężko pracować, aby udowodnić następującą właściwość algorytmu:
Twierdzenie: Niech będzie wyjściem algorytmu, a O rozwiązaniem optymalnym. Jeśli S różni się od O , to możemy dostosować O dostać innego rozwiązania O * , który różni się od O i ściśle lepiej niż O .S. O S. O O O∗ O O
Zauważ, dlaczego jest to przydatne. Jeśli twierdzenie jest prawdziwe, oznacza to, że algorytm jest poprawny. Jest to w zasadzie dowód sprzeczności. Albo jest taki sam jak O lub jest inny. Jeśli jest inaczej, możemy znaleźć inne rozwiązanie O ∗, które jest zdecydowanie lepsze niż O - ale to sprzeczność, ponieważ zdefiniowaliśmy O jako rozwiązanie optymalne i nie może być żadnego rozwiązania, które byłoby lepsze od tego. Jesteśmy więc zmuszeni stwierdzić, że S nie może różnić się od O ; S musi zawsze być równe OS. O O∗ O O S. O S. O , tj. chciwy algorytm zawsze wyświetla poprawne rozwiązanie. Jeśli możemy udowodnić powyższe twierdzenie, udowodniliśmy, że nasz algorytm jest poprawny.
W porządku. Jak więc udowodnimy roszczenie? Myślimy o rozwiązaniu jako wektorze ( S 1 , … , S n ), który odpowiada sekwencji n wyborów dokonanych przez algorytm, i podobnie myślimy o optymalnym rozwiązaniu O jako wektorze ( O 1 , … , o n ) odpowiadający sekwencji wyborów, które prowadziłoby do o . Jeśli S różni się od O , musi istnieć jakiś indeks i, gdzie S i ≠S. ( S1, … , Sn) n O ( O1, … , On) O S. O ja ; skupimy się na najmniejszym takim ja . Następnie będziemy podkręcać O zmieniając O trochę w I th pozycję na mecz S I , czyli będziemy podkręcać Rozwiązaniem optymalnym O zmieniając I th wybór do jednego, wybranego przez algorytm zachłanny, a następnie pokażemy, że prowadzi to do jeszcze lepszego rozwiązania. W szczególności, będziemy definiować O * za cośS.ja≠ Oja ja O O ja S.ja O ja O∗
z wyjątkiem tego, że często będziemy musieli nieznacznie zmodyfikować część aby zachować globalną spójność. Część strategii dowodowej polega na pewnej sprytności w odpowiednim zdefiniowaniu O ∗ . Następnie mięso dowodu będzie w jakiś sposób wykorzystywało fakty na temat algorytmu i problem wykazania, że O ∗ jest zdecydowanie lepsze niż OOi + 1, Oi + 2, … , On O∗ O∗ O ; tam potrzebujesz szczegółowych informacji na temat problemu. W pewnym momencie musisz zagłębić się w szczegóły konkretnego problemu. Ale to daje poczucie struktury typowego dowodu poprawności dla chciwego algorytmu.
Prosty przykład: podzbiór z maksymalną sumą
Może to być łatwiejsze do zrozumienia, szczegółowo omawiając prosty przykład. Rozważmy następujący problem:
Wejście: zbiór liczb całkowitych, liczba całkowita k Wyjście: zbiór S ⊆ U wielkości k, którego suma jest tak duża, jak to możliweU k
S.⊆ U k
Istnieje naturalny algorytm zachłanny dla tego problemu:
Losowe testy sugerują, że zawsze daje to optymalne rozwiązanie, więc formalnie udowodnijmy, że ten algorytm jest poprawny. Pamiętaj, że optymalne rozwiązanie jest wyjątkowe, więc nie będziemy musieli martwić się o więzi. Udowodnijmy roszczenie przedstawione powyżej:
Twierdzenie: Niech będzie wyjściem rozwiązania przez ten algorytm na wejściach U , k i O rozwiązaniem optymalnym. Jeśli S ≠ O , można skonstruować inne rozwiązanie O * , których suma jest nawet większy niż O .S. U, k O S.≠ O O∗ O
Dowód. Załóżmy, i pozwolić i jest indeksem pierwszej iteracji, gdzie x i ∉ O . (Taki indeks i musi istnieć, ponieważ przyjęliśmy S ≠ O, a według definicji algorytmu mamy S = { x 1 , … , x k } .) Ponieważ (z założenia) i jest minimalne, musimy mieć x 1 , … , x i - 1 ∈ O , w szczególności,S.≠ O ja xja∉ O ja S.≠ O S.= { x1, … , Xk} ja x1, … , Xi - 1∈ O ma postać O = { x 1 , x 2 , … , x i - 1 , x ′ i , x ′ i + 1 , … , x ′ n } , gdzie liczby x 1 , … , x i - 1 , x ′ i , … , x ′ nO O={x1,x2,…,xi−1,x′i,x′i+1,…,x′n} x1,…,xi−1,x′i,…,x′n są wymienione w kolejności malejącej. Patrząc na to, jak algorytm wybiera , widzimy, że musimy mieć x i > x ′ j dla wszystkich j ≥ i . W szczególności x i > x ′ i . Zdefiniuj więc O = O ∪ { x i } ∖ { x ′ i } , tzn. Otrzymamy O ∗ poprzez usunięcie i- tej liczby w Ox1,…,xi xi>x′j j≥i xi>x′i O=O∪{xi}∖{x′i} O∗ i O i dodając . Teraz suma elementów O * jest sumą elementów O Plus x I - x ' I , a x I - x ' I > 0 , więc wy * 's suma jest większa niż ściśle O ' sumy s. To potwierdza roszczenie. ◼xi O∗ O xi−x′i xi−x′i>0 O∗ O ■
Intuicja polega na tym, że jeśli chciwy algorytm dokona wyboru niezgodnego z , to możemy udowodnić, że O mógłby być jeszcze lepszy, gdyby został zmodyfikowany w celu włączenia elementu wybranego przez chciwy algorytm na tym etapie. Ponieważ O jest optymalne, nie może być żadnego sposobu, aby uczynić go jeszcze lepszym (byłoby to sprzecznością), więc jedyną pozostałą możliwością jest to, że nasze założenie było błędne: innymi słowy, zachłanny algorytm nigdy nie dokona wyboru które są niezgodne z o .O O O O
Ten argument jest często nazywany argumentem wymiany lub lematem wymiany . Znaleźliśmy pierwsze miejsce, w którym optymalne rozwiązanie różni się od chciwego rozwiązania i wyobrażaliśmy sobie, że wymienimy ten element na odpowiadający mu chciwy wybór (zamieniliśmy x ′ i na x i ). Niektóre analizy wykazały, że ta wymiana może jedynie poprawić optymalne rozwiązanie - ale z definicji optymalne rozwiązanie nie może zostać ulepszone. Jedyny wniosek jest taki, że nie może być miejsca, w którym optymalne rozwiązanie różni się od chciwego rozwiązania. Jeśli masz inny problem, poszukaj możliwości zastosowania tej zasady wymiany w konkretnej sytuacji.O x′i xi
źródło
then we can tweak O to get another solution O∗ that is different from O and strictly better than O
mnie myli. Jeśli istnieje wiele optymalnych rozwiązań, możliwe jest posiadanieS != O
i oba są optymalne; możemy dostosować O, aby było „bardziej podobne” do S (tworząc O ∗), a jednocześnie być tak dobre, jak (niestrictly better than
) O.a single, unique optimal solution
. Ponieważ pytanie dotyczy udowodnienia poprawności dowolnego chciwego algorytmu, chciałbym udzielić odpowiedzi w przypadkach, w których może istnieć wiele optymalnych rozwiązań. Minęło trochę czasu, odkąd to wszystko przestudiowałem, ale czy to nie wystarczy, aby udowodnić, że możesz wymienić każdy element O_i w dowolnym optymalnym rozwiązaniu O, które różni się od alg. rozwiązanie S z S_i i wciąż ma rozwiązanie O ', które nie jest gorsze niż O?Jako przykład użyję następującego prostego algorytmu sortowania:
Aby udowodnić poprawność, wykonuję dwa kroki.
Po pierwsze, wybieram odpowiednią funkcję kosztu, dla której mogę pokazać, że algorytm poprawia ją na każdym etapie.
Dowodzi to, że algorytm ostatecznie się kończy.
Liczba inwersji na posortowanej liście wynosi 0. Jeśli wszystko pójdzie dobrze, algorytm zmniejszy liczbę inwersji do 0. Musimy tylko pokazać, że nie utknie w lokalnym minimum.
Zazwyczaj dowodzę tego przez sprzeczność. Zakładam, że algorytm zatrzymał się, ale rozwiązanie jest nieprawidłowe. W tym przykładzie oznacza to, że lista nie jest jeszcze posortowana, ale nie ma sąsiadujących elementów w niewłaściwej kolejności.
Dowodzi to, że algorytm zatrzymuje się tylko podczas sortowania listy. I tak skończyliśmy.
źródło