Problem z QuickSort C++

0

Witam,
Napisałem program sortujący, który ma zaimplementowane szybkie sortowanie oraz jako pivot wybiera ostatni element.

Jeżeli program sortuje 100.000.000 randomowych liczb to działa poprawnie, jeżeli sortuje 3000 malejąco ułożonych liczb to również działa poprawnie.
Problemy pojawiają się jeżeli chcę posortować np. 150.000.000 randomowych liczb lub 4000 malejąco ułożonych liczb.

Dodam jeszcze, że piszę w Visual Studio 2019.

Będę bardzo wdzięczny za jakąkolwiek pomoc lub wskazówkę.

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;
void quickSort(long[], int);
void quickSortRecursion(long[], int, int);

int main() {
    int n = 4000;
    long* tab = new long[n];
    for (int i = 0; i < n; i++) {
        //tab[i] = rand()%2000000000;
        tab[i] = n - i;
    }
    cout << "Start sort" << endl;
    quickSort(tab, n);
    return 0;
}
void quickSort(long tab[], int n) {
    clock_t start = clock();
    quickSortRecursion(tab, 0, n - 1);
    cout << (clock() - start) / (double)CLOCKS_PER_SEC << "\n";
    //for (int i = 0; i < n; i++) cout << tab[i] << " ";
}
void quickSortRecursion(long tab[], int indexPierwszy, int indexOstatni) {
    if (indexPierwszy >= indexOstatni) return;
    int indexA = indexPierwszy, indexB = indexPierwszy, valPivot = tab[indexOstatni];
    for (indexA; indexA < indexOstatni; indexA++) {
        if (tab[indexA] <= valPivot) {
            int val = tab[indexA];
            tab[indexA] = tab[indexB];
            tab[indexB] = val;
            indexB++;
        }
    }
    tab[indexOstatni] = tab[indexB];
    tab[indexB] = valPivot;
    if (indexB > indexPierwszy) quickSortRecursion(tab, indexPierwszy, indexB - 1);
    if (indexB < indexOstatni) quickSortRecursion(tab, indexB + 1, indexOstatni);
}

Pojawia się następujący błąd:
error.png

2

Przepełnienie stosu występuje ponieważ gdy program przechodzi do funkcji/metody odkłada sobie na stos adres powrotu.
Normalnie nie ma takiego problemu, ponieważ program wskakuje do metody by zaraz z niej wyskoczyć i zdjąć odłożone wartości ze stosu.
Rekurencja niestety ma tę wadę, że każdy kolejny krok jedynie odkłada kolejne wartości na stos, ale nic z nich nie zdejmuje (ponieważ program jeszcze nie wrócił).
Co z kolei przekłada się na bardzo szybko rosnące zużycie pamięci, które po jej wyczerpaniu prowadzi właśnie do takiego błędu jaki otrzymujesz.

edit: Skoro używasz Visual Studio, możesz to zaobserwować sprawdzając okno call stack - stos wywołań.
Zobaczysz w nim tę samą metodę wywołaną ogromną liczbę razy.

Spróbuj przemyśleć w jaki sposób oszczędzić pamięć i/lub zredukować liczbę rekurencyjnych skoków.

0

@Wojowni 1610:
Napisane powyżej to prawda, ale podając "quicksortowi" posortowane dane i jako piwot wybierając ostatni element, masz złożoność O(n), czyli na stosie odkłada się 40 tys. longów; swoją drogą, coś musisz mieć nie tak z pamięcią, bo to trochę mało, żeby przepełnić stos.
Spróbuj chociaż tak:

int valPivot = tab[(int)((indexOstatni + indexPierwszy) / 2)];

Dalej będzie wolne, ale może się nie wywali.

6

int valPivot = tab[(indexOstatni+indexPierwszy)>>1];
Wg mnie to ten kod prosto z kawału:

Baca wraca z Afryki, sąsiad go pyta:
- Co widziałeś?
- Zebrę widziałem!
- Jak wygląda?
- Konia znasz?
- Tak!
- To samo tylko w paski.
- Co jeszcze widziałeś?
- Żyrafę widziałem!
- Jak wygląda?
- Konia znasz?
- Tak!
- To samo tylko z długą szyją.
- Co jeszcze widziałeś?
- Krokodyła widziałem!
- Jak wygląda?
- Konia znasz?
- Tak!
- To zupełnie nie podobny!

Więc ten "quicksort" z prawdziwym quicksort'em to jak koń z krokodyłem.

4

Domyślny rozmiar stosu pod Visual C++ to tylko 1 MB. To za mało dla tego kodu.

Po ustawieniu 2 MB (2097152 B) program przestaje się wywalać.

a.png

0

@Azarien: Wielkie dzięki za pomoc, dzięki tej zmianie program sortuje poprawnie i się nie wysypuje!

0
Wojowni 1610 napisał(a):

@Azarien: Wielkie dzięki za pomoc, dzięki tej zmianie program sortuje poprawnie i się nie wysypuje!

Zostało tylko 3 drobne małoistotne problemy:

  • Koszt algorytmu O(n^2)
  • Użycie stosu O(n)
  • Wcale nie jest to QuickSort tylko InsertionSort - zakamuflowany nie do poznania.
0
_13th_Dragon napisał(a):

Zostało tylko 3 drobne małoistotne problemy:

  • Koszt algorytmu O(n^2)
  • Użycie stosu O(n)
  • Wcale nie jest to QuickSort tylko InsertionSort - zakamuflowany nie do poznania.

Już poprawiłem kod do QuickSortu, ale bez rady @Azarien program wywalał się, gdy dostawał duży zbiór liczb A-kształtny.
Jeżeli się nie mylę to A-kształtny zestaw liczb jest jednym z gorszych ułożeń liczb dla QuickSorta, jeżeli pivot jest środkową wartością.

1 użytkowników online, w tym zalogowanych: 0, gości: 1