Dziękuję wszystkim za odpowiedzi! Zdaję sobie sprawę, że to pytanie zostało zadane prawie dwa lata temu i odpowiedź od dawna została przyjęta, ale jestem nieco zdezorientowany przez odpowiedzi. Tak więc, mimo że pytający prawdopodobnie nie dba o nowe odpowiedzi, chciałbym dodać moją wersję, na wypadek, gdyby inni czytelnicy również byli zdezorientowani i udokumentowali moje własne podejście. Niektóre z nich mogły być bardziej odpowiednie jako komentarze, ale nie mam jeszcze reputacji do komentowania.
Po pierwsze, bez względu na to, jak często na nią patrzę - na tablicy lub w debugerze - nie mogę sobie pozwolić na rezygnację z pętli w pierwszym węźle, tj.wskazując na siebie, jeśli nie używam warunkowego rozróżnienia między przypadkami sąsiednich węzłów a nie, nawet z abstrakcyjnymi krokami lub konkretnym kodem z obecnie akceptowanej odpowiedzi Smashery'ego. Rozglądałem się trochę po Internecie, aby znaleźć kod w stylu aktualnie akceptowanej odpowiedzi, aby uniknąć takiego warunku, ale dla mnie jest to zaskakująco trudne, a moje wyszukiwania nie znalazły takiego rozwiązania (chyba, że się mylę o proponowanym, który prawdopodobnie jest nieprawidłowy). Jeśli istniało jakieś sprytne wyrażenie, które dawałoby adres pierwszego węzła, gdy sąsiadują z nim, oraz adres następcy pierwszego węzła, gdy nie są, to warunkowe nie byłoby potrzebne, ponieważ znalezienie nowego następcy węzła to, co (widocznie) wymusza to warunkowe.
Po drugie, mam to samo pytanie o kolejne przypisania do tych samych zmiennych w zaakceptowanej odpowiedzi, co inni komentatorzy. Mam nadzieję, że nie jestem tutaj wyjątkowo gęsty, ale przypisuję różne wartości tej samej zmiennej w sekwencji, chyba że w przypadku efektów ubocznych, dla mnie nigdy nie wydaje się, że zostawiam zmienną o innej wartości niż ostatnie zadanie, bez znaczenia jaka konfiguracja węzłów biorę pod uwagę, a tym samym sprawia, że poprzednie zadania wydają się nadmiarowe. Jeśli się mylę i to podejście faktycznie rozwiązałoby ten problem, to byłbym w stanie wyeliminować ostatnie warunkowe w kodzie poniżej, który starałem się pozbyć, gdy po raz pierwszy spojrzałam w Internecie na rozwiązanie problemu. przełączanie węzłów bez specjalnych obudów sąsiednich węzłów. Nie jestem do końca pewny, ale brzmiało to tak, jak Smashery celowo zostawił te powtarzające się zadania z logicznego rygoru i lepiej zilustrował procedurę - być może mnie jednak źle zrozumiałem.
Po trzecie, na tej i innych stronach często widziałem, że stwierdzenie z innych odpowiedzi powtarzało, że lepiej jest zamienić zawartość węzłów, niż wskaźniki. Oczywiście w przypadku zwykłych liczb całkowitych, jak w przykładach do tej pory, to oczywiście daje krótszy, prostszy kod. Jednakże, gdy omawiamy połączone listy z węzłami zawierającymi liczby całkowite, zwykle jest to stand-in dla struktury danych zabezpieczających bardziej złożonego i generycznego kontenera. W związku z tym nie sądzę, że zamiana zawartości węzłów jest naprawdę łatwa, przynajmniej jeśli implementacja struktury danych nie może przyjąć założeń dotyczących semantyki kopiowania elementów kontenera. Ponadto możliwość zamiany zawartości takich węzłów oznacza dla mnie, że połączona lista ma własność zawartości tych węzłów, ponieważ w przeciwnym razie kod spoza metody listy połączonej może zawierać odniesienia do obiektów w tych węzłach, których wartości nagle zmieniają się pod nimi.
Przyznam jednak, że może to zależeć od semantyki kontenera. W przypadku tablicy można oczekiwać, że metoda wymiany zmieni wartość pod odnośnikami do określonego indeksu tej tablicy. Oznaczałoby to, że odniesienia nie mają odnosić się do konkretnego obiektu, ale do pozycji w pojemniku, który można indeksować. Jeśli uznamy listę połączoną za środek do zamawiania tylko zestawu obiektów, które mają swoje zastosowanie poza połączoną listą, użytkownik prawdopodobnie oczekiwałby, że operacja wymiany będzie tylko wymieniać pozycję, a nie zawartość.
Wyobraź sobie na przykład, że połączona lista reprezentuje obiekty typu "samochód". Każdy samochód ma właściciela i ten właściciel odwołuje się do samochodu za pomocą wskaźnika do niego. Przypuśćmy teraz, że połączona lista reprezentuje porządek, w którym zestaw samochodów ma być obsługiwany do wglądu u dealera samochodowego. Gdybyśmy wymienili zawartość dwóch węzłów, w celu wymiany miejsc na dwa samochody i dokonali tego poprzez wymianę ich zawartości, to obsługa w rzeczywistości odbyłaby się w nowej, właściwej kolejności - ale ludzie również nagle otrzymaliby różne samochody! (Nie miałbym nic przeciwko zamianie Tesli, ponieważ jeżdżę tylko Corollą.)
Jeśli połączona lista była, jak w przykładzie tablicy, na podstawie semantyki indeksowania, to pozycja w węźle może po prostu reprezentować kolejność, w jakiej samochody są ładowane na statek do transportu. W tym momencie samochody nie mają żadnych właścicieli, a my naprawdę tylko dbają o to, w jakim są gnieździe. Wtedy, przypuszczam, że naprawdę nie zaszkodzi wymieniać samochody, tj. Zawartość obiektów przywoływanych przez węzły.
Wreszcie do kodu. Jak już wspomniałem powyżej, nie udało mi się uniknąć specjalnej obudowy dla sąsiednich węzłów.
pierwsze, określenie metody pomocnicze:
int find_node(int v, node* root, node** n, node** pn) {
int i = 0;
for (*n = root; *n != NULL && (*n)->v != v; ++i) {
*pn = *n;
*n = (*n)->next;
}
return i;
}
ten sposób znajduje się węzeł, od jego wartości. Zwrócona liczba całkowita jest pozycją zerową (nazwij ją indeksem, jeśli chcesz) węzła na liście połączonej. Zauważyłem, że wykrywanie sąsiedztwa za pomocą pozycji, a nie porównania wskaźnika, jest bardziej czytelne. Początkiem listy jest root. Metoda ustawia n, aby wskazywało węzeł zawierający przekazaną wartość. W pn, metoda przechowuje poprzednika n.
co następuje faktyczna zamiana:
void swap_nodes(node **root, int v1, int v2) {
if (v1 == v2) return;
node *n1, *n2, *pn1, *pn2;
int p1 = find_node(v1, *root, &n1, &pn1);
int p2 = find_node(v2, *root, &n2, &pn2);
if (p1 > p2) {
std::swap(n1, n2);
std::swap(pn1, pn2);
std::swap(p1, p2);
}
if (p1 == 0) *root = n2;
else pn1->next = n2;
node* nn1 = n1->next;
n1->next = n2->next;
if (p2 - p1 > 1) {
n2->next = nn1;
pn2->next = n1;
} else {
n2->next = n1;
}
}
przykro mi, że zmieniłem podpis metody PO trochę. Zauważyłem, że wygodniej jest przekazywać wartości węzłów do zamiany, w przeciwieństwie do wskaźników węzłów. Jeśli przekażesz wskaźniki węzłów tylko do odpowiednich węzłów, będziesz musiał wykonać kolejne przejście, aby znaleźć poprzedników za pomocą tego rozwiązania, co było dla mnie nieco kłopotliwe. Jeśli nie możemy rozróżnić węzłów według tych wartości, np. wartości nie są unikalne, ale potrzebujemy wskaźników do węzłów.
Podobnie jak w przypadku wyjaśnienia dla powyższego find_node, najpierw znajdujemy pozycje, węzły i poprzedniki dla wartości węzłów przekazanych do swap_nodes poprzez v1 i v2. Wartości dla pierwszego i drugiego węzła są zamieniane, gdy drugi węzeł do zamiany pojawia się przed pierwszym. Nie ma w tym wiele kodu, redukuje się specjalne obudowy i sprawia, że wizualizacja jest nieco łatwiejsza.
Teraz pozostało nam jeszcze tylko dwa warianty, z których żaden nie wydawał się banalny. Jeśli pierwszy węzeł znajduje się na czele listy połączonej, tj. W pozycji zero, to root musi wskazywać na drugi węzeł. W przeciwnym razie poprzednik pierwszego węzła wskaże drugi węzeł.
Poprzednia wartość pierwszego następcy węzła musi zostać zapamiętana, na wypadek, gdyby węzły nie byłysiadowały ze sobą. Następnie następca pierwszego węzła jest ustawiony na bieżącego następcę drugiego węzła. Jest to jedyna zmiana, która ma zastosowanie do wszystkich przypadków: nowy następca pierwszego węzła będącego starym następcą drugiego węzła jest jedyną pewną i pomocną w rozpoczęciu w celu zapamiętania operacji wskaźnika i ich sekwencji podczas wdrażania tego zamiany .
Wreszcie, jeśli pozycje węzłów różnią się o więcej niż jeden, nie sąsiadują ze sobą. Następnie nowy następca drugiego węzła staje się starym następcą pierwszego węzła - zapisanym powyżej - a poprzednik drugiego węzła wskazuje teraz na pierwszy węzeł. Jeśli sąsiadują ze sobą, nie ma węzłów między węzłami do zamiany, które wymagają aktualizacji, więc wystarczy połączyć drugi węzeł z pierwszym, aby to zrobić.
Hej, Reti, odpowiedziałem na edit1 w mojej odpowiedzi. Jeśli otrzymujesz błąd segfault, sprawdź, czy poprawnie przypisujesz i usuwasz zmienną tmp. – Smashery