Witam. Muszę napisać algorytm, który w std wejściu dostaje z liczb, i musi na std wyjściu wyświetlić te liczby podzielone na dwie równe połowy, np. dostaję liczby:
Napisz co masz zrobić w sytuacji, gdy suma tych liczb wejściowych nie jest liczbą parzystą (a więc nie jest podzielna przez 2). Z tego co widzę w tym przykładzie zignorowałeś jedno 13, ale dlaczego akurat 13?
Załóżmy, że suma liczb wejściowych jest parzysta, ale z żadnej kombinacji tych liczb nie da się uzyskać takiej sumy, która będzie równa połowie sumy całkowitej. Co w takiej sytuacji należy zrobić?
Czy masz wyświetlić wszystkie możliwe kombinacje liczb, czy też wystarczy tylko jedna? Tzn. w tym przykładzie poprawnym wynikiem jest też np.
26, 12, 10, 2, 13, 99, 58
oraz
55, 44, 44, 39, 22, 16
Przynajmniej tak mi zwrócił program. Chyba, że czegoś nie rozumiem i chodzi tu o coś zupełnie innego.
To te zaznaczanie wpisać do tablicy bool’owskiej i później w pętli sobie sprawdzań od ostatniego elementu który (pierwszy)element ma 1 odznaczam go i aktualny indeks zwiększam o ta liczbe+1 ?!
W ramach treningu napisałem sobie ten programik w C#. Poniżej zamieszczam exe (do jego uruchomienia konieczne jest zainstalowanie .NET Framework 4 na komputerze). W programie można wybierać, czy chce się szukać tylko jedno rozwiązanie, czy też wszystkie możliwe. Wyszukiwanie jednego rozwiązania jest w miarę szybkie. Natomiast wyszukiwanie wszystkich możliwych rozwiązań w sytuacji, gdy jest więcej niż 12 liczb wejściowych trwa już zdecydowanie za długo. Próbowałem poprawić ten kod, aby przyspieszyć cały proces, ale na razie bezskutecznie. Jakbyś ktoś miał jakiś pomysł, co można zmienić to chętnie posłucham. Poniżej zamieszczam kod kluczowej funkcji.
public List Numbers
{
get { return numbers; }
}
...
public void FindNumbers(bool findOneSolution)
{
this.ResetVariables();
if (NumbersSum % 2 != 0)
throw new Exception("Suma podanych liczb wynosi " + NumbersSum.ToString() + ", a więc nie jest liczbą parzystą.");
startTime = DateTime.Now;
this.FindNumbers(new HashSet(), 0, 0, NumbersSum / 2, findOneSolution);
stopTime = DateTime.Now;
}
private void FindNumbers(HashSet numbersKeys, int numbersAdded, int sum, int numbersHalfOfTheSum, bool findOneSolution)
{
if (numbersAdded == Numbers.Count || (findOneSolution && Results.Count > 0))
return;
for (int i = 0; i < Numbers.Count; i++)
{
if (!numbersKeys.Contains(i))
{
int sumCopy = sum + Numbers[i];
HashSet numbersKeysCopy = numbersKeys.Clone();
numbersKeysCopy.Add(i);
if (sumCopy == numbersHalfOfTheSum && !(findOneSolution && Results.Count > 0))
{
int index = Results.AddResultItem(numbersKeysCopy, Numbers);
if (index != -1)
{
this.OnResultFound(this, new EventArgs());
return;
}
}
else if (sumCopy < numbersHalfOfTheSum)
this.FindNumbers(numbersKeysCopy, numbersAdded + 1, sumCopy, numbersHalfOfTheSum, findOneSolution);
}
}
}
PS 1) Jak ktoś bardzo będzie chciał to wrzucę cały kod.
PS 2) Dla liczb podanych w przykładzie (99 58 55 44 44 39 26 22 16 13 12 10 2) wynik otrzymałem w czasie 00:03:38.3784906 na laptopie sprzed dwóch lat. Otrzymałem takie wyniki:
matzu , gdyby przeczytałeś ten mój algorytm i na jego podstawie zrealizowałeś kod to wynik dla 13 liczb obliczał by wszystkie warianty w niecałą milisekundę.
Próbowałem sam z tym powalczyć Może ostatecznie użyję tego Twojego rozwiązania, ale na razie po kolei sprawdzam swoje pomysły i patrzę co jest nie tak. Udało mi się poprawić trochę ten mój kod i teraz na tym moim laptopie wynik dla wszystkich tych liczb podanych przez autora tematu dostaję w niecałe 13 sekund. Nie jest to wprawdzie jedna milisekunda, ale progress jest. Poprawiłem też trochę GUI programu.
public void FindNumbers()
{
startTime = DateTime.Now;
this.ResetVariables();
int searchedSum = NumbersSum / 2;
bool[] usedIndexes = new bool[Numbers.Length];
int percentComplete = 0;
for (int i = 0; i < Numbers.Length; i++)
{
FindNumbers(i, 0, searchedSum, usedIndexes);
percentComplete = (int)(((float)((i + 1) * 2) / (float)(Numbers.Length * 2)) * 100);
this.OnProgressChanged(this, new ProgressChangedEventArgs(percentComplete));
}
stopTime = DateTime.Now;
}
public void FindNumbers(int currentIndex, int currentSum, int searchedSum, bool[] usedIndexes)
{
bool goFurther = true;
currentSum += Numbers[currentIndex];
usedIndexes[currentIndex] = true;
if (currentSum == searchedSum)
{
Results.AddResultItem(usedIndexes, Numbers);
goFurther = false;
}
else if (currentSum > searchedSum)
goFurther = false;
if (goFurther)
{
for (int i = 0; i < usedIndexes.Length; i++)
{
if (!usedIndexes[i])
this.FindNumbers(i, currentSum, searchedSum, usedIndexes);
}
}
usedIndexes[currentIndex] = false;
}
Problem polega na tym, że nie wiem jeszcze jak zabezpieczyć się przed sprawdzaniem tego samego rozwiązania. Załóżmy, że jest tablica trzech liczb, co daje 3! wszystkich możliwych kombinacji. Przy czym tylko 3 z nich powinny być sprawdzane, tj.
012
021
102
bo pozostałe trzy tj.
120
201
210
to są już niejako kopie trzech poprzednich. W tym momencie sprawdza mi wszystkie możliwe kombinacje, tzn. wszystkie gałęzie drzewa i dopiero w odpowiedniej metodzie (AddResultItem) sprawdzam, czy takie rozwiązanie już zostało znalezione, czy też nie. Gdybym nie musiał wykonywać tego sprawdzania oraz nie musiał przeszukiwać niepotrzebnych gałęzi to powinienem zyskać kolejne kilka sekund. Tak, czy siak zamieściłem kod i program może się komuś przyda.
No faktycznie nieprecyzyjnie się wyraziłem. Liczby posortowałem nierosnąco, bo faktycznie 44 się powtarza. Zresztą czy bym posortował nierosnąco, czy niemalejąco to rozwiązanie bym nie dostał. A dlatego pominąłem część kombinacji, bo zasugerowałem się tym co napisałeś tutaj (wypisałem je sobie analogicznie):
np. tutaj nie sprawdzasz w ogóle 0+1+3. Teraz zresztą zauważyłem, że w ogóle tutaj nie uwzględniasz liczby o indeksie 4.
Masz racje coś mi się niepoprawnie wypisało dla 4’ch oraz 5’ciu liczb.
Właściwie to jak odpalisz ten algorytm co napisałem nieco wyżej to on wiele kombinacji pomija grupowo.
Powiedzmy jeżeli dodałeś elementy 0+1 i jest to więcej niż polowa to automatycznie wszystkie pozostałe kombinacji zawierające jedynkę już są pomijane, przechodzi od razu do kombinacji 0+2, no i następne.
OK, rozumiem. To by pasowało. W ten sposób rzeczywiście można znacznie zredukować ilość kombinacji do sprawdzenia. Niby takie oczywiste, a o tym nie pomyślałem /facepalm.
EDIT: Zmodyfikowałem trochę ten swój poprzedni kod uwzględniając częściowo to o czym mówiliśmy. Czas znalezienia wszystkich rozwiązań dla liczb z przykładu spadł do poniżej 90ms (na tym moim laptopie).