Sortowanie listy przez łączenie naturalne z użyciem klas
Mariusz:
https://pastebin.com/Dz9B8KMa
Próbowałem dopisać sortowanie listy dwukierunkowej przez łączenie naturalne do klasy
ale chyba gdzieś popełniłem błąd
25 mar 19:53
Pytający:
Zastanów się, co robią destruktory zmiennych lokalnych po zakończeniu funkcji
DoublyLinkedList<T>::MergeSort.
25 mar 20:33
Mariusz:
Tak ale co z tym zrobić próbowałem dopisać listę do argumentów funkcji i
przekazać przez referencję
Próbowałem też zwrócić listę i nic to nie dało
25 mar 21:25
Mariusz:
Rzeczywiście musi być coś z destruktorami bo jak przeniosłem
zmienne lokalne do argumentów funkcji to już zaczęło działać chociaż to
niezbyt eleganckie rozwiązanie
25 mar 22:20
Pytający:
W linii 459 masz sytuację:
L1.first == A // niech A oznacza głowę posortowanej listy
L1.last == B // niech B oznacza głowę posortowanej listy
L2.first == nullptr
L2.last == nullptr
this−>first == nullptr
this−>last === nullptr
Po linii 461 (przed wyjściem z funkcji) masz sytuację:
L1.first == A
L1.last == B
L2.first == nullptr
L2.last == nullptr
this−>first == A
this−>last == B
Po zakończeniu funkcji kończy się zakres zmiennych L1, L2, więc są wywoływane ich destruktory,
a te wyglądają przecież tak:
while(!isEmpty()) // isEmpty sprawdza, czy głowa jest nullem
{
deleteFirst();
}
Czyli destruktor L2 nie zrobi w zasadzie nic, ale destruktor L1 usunie wszystkie węzły z listy.
Są to oczywiście te same węzły, na które wskazują wskaźniki z aktualnego obiektu (wskazywanego
przez this).
Czyli po linii 462 masz sytuację:
this−>first == invalid pointer value
this−>last == invalid pointer value
// to co było wskazywane przez te wskaźniki zostało zwolnione przez odpowiednie wywołania
delete, więc odwoływanie się do wskazań tych wskaźników jest niezdefiniowane
//
https://stackoverflow.com/a/44182938
// L1, L2 już nie są zdefiniowane
Wystarczy jedna linijka na koniec funkcji i będzie hulać (destruktor L1 nie usunie żadnego
węzła):
L1.setFirst(nullptr); // ogon w teorii też wypadałoby wyzerować, ale nie jest to niezbędne przy
takiej implementacji
Jako tako elegancko byłoby pewnie przeciążyć kopiujący operator przypisania listy (i tam
podmienić wskaźniki) i wywołać go po posortowaniu:
*this = std::move(L);
ale to chyba za dużo...
25 mar 22:56
26 mar 08:10
Pytający:
"jest jeszcze taka dość ważna umiejętność jak czytanie kodu"
"Koleś" zawsze wywołuje merge po split, Ty zawsze wywołujesz Merge o 1 raz mniej niż Split (i
po ostatnim wywołaniu Split masz listę w L1, nie w *this). Poza tym u niego merge_sort jest
wywoływany rekurencyjnie, więc masz wiele różnych (*this), ale na koniec każdego wywołania
merge_sort listy h1, h2 są u niego puste, a "cała" lista (aktualnie przetwarzana w kroku
rekurencyjnym) jest w *this.
26 mar 12:12
Mariusz:
Może udałoby się wyrugować funkcje headdel, tailins
lub w nazewnictwie jakie zaproponowałeś ListInsertNodeTail ,ListDetachHead
mając funkcje takie jak
SplitAfter, SplitBefore , Concat
SplitAfter − rozdziela listę za wybranym węzłem
SplitBefore − rozdziela listę przed wybranym węzłem
Concat − proste łączenie list tzw konkatenacja
Jeden zaproponował taki spis kroków dla funkcji SplitAfter
1 zapamiętać wskaźnik na głowę listy wejściowej,
2 znaleźć węzeł, który będzie ostatnim węzłem pierwszej listy docelowej,
3 zapamiętać wskaźnik na Next węzła znalezionego w punkcie 2.,
4 wpisać do Next wartość nil węzła znalezionego w punkcie 2.,
5 wpisać do Prev wartość nil węzła zapamętanego w punkcie 3.,
6 zwrócić wskaźnik na węzeł zapamiętany w punkcie 1.,
jako wskaźnik na głowę pierwszej listy docelowej,
7 zwrócić wskaźnik na węzeł zapamiętany w punkcie 3.,
jako wskaźnik na głowę drugiej listy docelowej;
Jednak tak jak podejrzewałem nie uwzględnia on przypadków brzegowych
https://ideone.com/ed5S4j
Ja tutaj próbowałem zapisać funkcję na podstawie tej listy kroków
Nie wiem czy uwzględniłem wszystkie przypadki brzegowe
Dlaczego po usunięciu list L1 oraz L2 wskaźniki głowy i ogona listy L nie pokazują na null
Czy ustawienie głowy i ogona listy L na null nie spowoduje wycieków pamięci
26 mar 20:44
Pytający:
"Dlaczego po usunięciu list L1 oraz L2 wskaźniki głowy i ogona listy L nie pokazują na null "
A dlaczego miałyby pokazywać? Przecież nigdzie nie modyfikujesz tych wskaźników. Masz sytuację
podobną do tej wyżej opisanej − to na co wskazują odpowiednio głowa i ogon listy L usunąłeś
poprzez wywołania delete.
27 mar 18:34
2 kwi 09:21
Mariusz:
Gdybyśmy scalali do dwóch list zamiast do jednej to funkcję rozdzielającą
można by było wywołać tylko raz
Ten pomysł widziałem w książce Diksa i Ryttera
2.10.1
Scalanie wielofazowe z 4 plikami
Załóżmy że bloki zostały rozłożone na pliki P0 i P1
Pliki P2 i P3 są początkowo puste
i1 = 0; i2 = 1; // numery plików wejściowych , tzn otwartych do czytania
j1 = 2; j2 = 3; // numery plików wyjściowych , tzn otwartych do pisania
while jest wiecej niz jeden blok do
(a) scal pierwszy blok na Pi1 z pierwszym blokiem na Pi2 i powstający blok
zapisz na Pj1
(b) scal następny blok na Pi1 z następnym blokiem na Pi2 i powstający blok
zapisz na Pj2
(c) powtarzaj kroki (a) i (b) (kładąc powstające bloki na przemian na pliki Pj1 i
Pj2)
aż zostanie osiągnięty koniec plików Pi1 i Pi2
(d) przewiń wszystkie pliki do początku; dodaj liczbę 2 mod 4 do i1,i2,j1,j2
odwracając w ten sposób rolę plików wejściowych
3 kwi 22:27