Problem wydawania reszty (algorytm zachłanny)

Algorytm zachłanny wydawania reszty (1) Przykład rozwiązania problemu wydawania reszty za pomocą algorytmu zachłannego. Jak widać, rozwiązanie znalezione przez algorytm nie jest rozwiązaniem optymalnym
REKLAMA C++. Algorytmy i struktury danych
−40%62,37 zł
Deep Learning. Praca z językiem Python i biblioteką Keras
−40%35,40 zł
Automatyzacja nudnych zadań z Pythonem. Nauka programowania
−30%62,30 zł
Opus magnum C++11. Programowanie w języku C++ (komplet)
−40%89,40 zł

Ten artykuł opisuje algorytm zachłanny rozwiązujący problem wydawania reszty. Algorytm ten polega na wybieraniu zawsze największej dostępnej monety, tzn. takiej, która nie jest większa od kwoty pozostałej do wydania.

Algorytm nie zawsze znajduje rozwiązanie optymalne. Przykładowo, dla zbioru nominałów {1, 3, 4} i kwoty 6 algorytm użyje najpierw monety o nominale 4 (pozostaje do wydania kwota 2), potem monety o nominale 1 (pozostaje kwota 1) i jeszcze raz monety o nominale 1. Łącznie algorytm użyje więc trzech monet, podczas gdy rozwiązanie optymalne wymaga użycia tylko dwóch (dwie monety o nominale 3).

Opis działania algorytmu

Dany jest posortowany rosnąco ciąg nominałów A=(c1, c2, …, cn) oraz kwota do wydania r. Algorytm działa następująco:

  1. Przypisujemy współczynnikom k1, k2, …, kn wartości 0.
  2. Dopóki r jest większe od zera:
    • Jeżeli r jest mniejsze od cn, zmniejszamy n o 1.
    • W przeciwnym razie zwiększamy wartość kn o 1 i zmniejszamy wartość r o cn.

Rozwiązania optymalne

Choć w przypadku ogólnym algorytm zachłanny nie daje rozwiązania optymalnego, to istnieją takie zbiory nominałów, dla których algorytm tę gwarancję daje. W pracy Combinatorics of the change-making problem (link w bibliografii) zaprezentowano metodę pozwalającą sprawdzić, czy dla danego ciągu nominałów algorytm zachłanny daje gwarancję znalezienia rozwiązania optymalnego niezależnie od wydawanej kwoty (takie ciągi nominałów w dalszej części artykułu będziemy określać jako „zachłanne”). Posortowany ciąg nominałów A=(c1, c2, …, cn) jest „zachłanny”, jeśli są spełnione następujące warunki:

  • Ciąg A′=(c1, c2, …, cn-1) jest „zachłanny”.
  • Liczba monet wyznaczona przez algorytm zachłanny dla ciągu nominałów A′ i kwoty mcn-1-cn jest mniejsza bądź równa m-1, gdzie wartość m to zaokrąglony w górę iloraz dwóch największych nominałów (cn/cn-1).

Dowód tego twierdzenia jest dostępny we wspomnianej pracy Combinatorics of the change-making problem. Znając powyższe warunki możemy sprawdzić kolejno zachłanność podciągów (c1, c2), (c1, c2, c3) aż do (c1, c2, …, cn).

Dla przykładu sprawdźmy, czy „zachłanny” jest ciąg nominałów (1, 2, 5, 10):

  • Jednoelementowy ciąg 1 musi być „zachłanny”, gdyż dla każdej kwoty istnieje tylko jedno rozwiązanie.
  • Dla ciągu (1, 2) wartość m wynosi 2. Kwota mcn-1-cn wynosi zatem 0. Algorytm zachłanny do wyznaczenia tej kwoty użyje 0 monet. 0 jest mniejsze niż 2-1, zatem ciąg jest „zachłanny”.
  • Dla ciągu (1, 2, 5) wartość m wynosi 3. Kwota mcn-1-cn wynosi zatem 1. Algorytm zachłanny do wyznaczenia tej kwoty użyje 1 monety. 1 jest mniejsze niż 3-1, zatem ciąg jest „zachłanny”.
  • Dla ciągu (1, 2, 5, 10) wartość m wynosi 2. Kwota mcn-1-cn wynosi zatem 0. Algorytm zachłanny do wyznaczenia tej kwoty użyje 0 monet. 0 jest mniejsze niż 2-1, zatem ciąg jest „zachłanny”.

Z tego twierdzenia wynikają też pewne bardziej ogólne wnioski. Zastanówmy się nad ciągami, w których każdy element ciągu jest wielokrotnością poprzedniego (kcn-1=cn, k > 0). Wówczas iloraz cn/cn-1 wynosi dokładnie k. Kwota mcn-1-cn wynosi wówczas 0. Do wydania kwoty 0 potrzeba 0 monet. 0 zawsze jest mniejsze bądź równe k-1. Podsumowując, jeśli w ciągu nominałów każdy element (poza pierwszym) jest wielokrotnością poprzedniego, to algorytm zachłanny będzie dawał rozwiązania optymalne.

Złożoność obliczeniowa

Dla kwoty 1 i n nominałów główna pętla programu wykona się n razy. Dla zbioru nominałów (1) i kwoty r pętla wykona się r razy. Można zatem powiedzieć, że złożoność czasowa algorytmu to O(n+r). Warto jednak zauważyć, że w praktyce algorytm może działać jeszcze szybciej – dla ustalonego r liczba wykonań pętli może wręcz maleć wraz ze wzrostem n, dopóki cn<r. Złożoność pamięciowa algorytmu jest stała (nie zależy od n ani r).

Bibliografia

  • A. Adamaszek, M. Adamaszek, Combinatorics of the change-making problem, European Journal of Combinatorics, Volume 31, Issue 1, 2010, s. 47-63, DOI: 10.1016/j.ejc.2009.05.002.
  • Z.J. Czech, S. Deorowicz, P. Fabian, Algorytmy i struktury danych. Wybrane zagadnienia, Wydawnictwo Politechniki Śląskiej, Gliwice, 2010, ISBN 9788373356689.
Ocena: +3 Tak Nie
Liczba głosów: 3.

Dodano: 6 października 2016 12:33, ostatnia edycja: 30 stycznia 2019 14:08.

REKLAMA

Zobacz też

Algorytm Zhanga-Suena – algorytm służący do szkieletyzacji obrazu binarnego. Szieletyzacja polega na wyborze z obrazu binarnego tych pikseli, które są równo odległe od krawędzi obiektu.

→ Czytaj całość

Dziel i zwyciężaj (ang. divide and conquer) – technika projektowania algorytmów polegająca na podejściu rekurencyjnym. W technice tej problem dzielony jest na mniejsze podproblemy, te podproblemy na jeszcze mniejsze podproblemy, aż dojdzie się do przypadków trywialnych (np. posortowanie jednoelementowej tablicy, obliczenie silni z 1).

Jeśli rozpatrywany problem wymaga podzielenia na podproblemy, jest on określany jako przypadek rekurencyjny. Jeśli mamy do czynienia z przypadkiem trywialnym, jest to przypadek bazowy. Tworząc algorytm wykorzystujący metodę dziel i zwyciężaj musimy ustalić:

  • Jak rozwiązać przypadek bazowy (trywialny).
  • Jak wyznaczyć rozwiązanie problemu, mając dostępne rozwiązania podproblemów.

Przykładem algorytmu opartego na tej metodzie jest sortowanie przez scalanie.

→ Czytaj całość

Algorytm – przepis, zbiór poleceń, opis ciągu operacji prowadzących do rozwiązania konkretnego problemu. Algorytm możemy również rozumieć jako funkcję przekształcającą dane wejściowe w dane wyjściowe.

Algorytm musi być skończony, czyli jego zapis ma składać się ze skończonej liczby znaków. Musi również być poprawny, czyli dla wszystkich możliwych danych wejściowych powinien zwracać prawidłowy wynik (może być nim informacja o braku rozwiązania). Algorytm musi wykazywać również własność stopu – niezależnie od danych wejściowych obliczenia algorytmu powinny dochodzić do punktu końcowego, czyli po prostu kończyć się (nie mogą np. wpadać w nieskończoną iterację). Zapis algorytmu musi być precyzyjny, bez jakichkolwiek niejasności.

→ Czytaj całość
Polityka prywatnościKontakt