Wielopoziomowe sortowanie listy jednokierunkowej

0

Siedzę nad tym już zbyt wiele godzin, "trochę" mnie to przerasta.
Mam taką prostą listę jednokierunkową:

struct Node {
Node * next;
int data1, data2, data3, data4, data5;
}

Dane do listy wczytuję z pliku .txt.
Mam więc już wypełnioną danymi listę i teraz: muszę ją posortować według data1, później według data2 (w taki sposób, żeby nadal było posortowane według data1), później według data3 w taki sposób aby nadał było posortowane według data2 i data3, i tak dalej aż do data5.

Przykładowa zawartość .txt:
<data1> <data2> <data3> <data4> <data5>

6 1 1 1 1
5 2 6 3 2
1 3 1 1 5
5 2 6 3 8
1 2 1 1 4
2 1 5 3 7

Output powinien być:

1 2 1 1 4
1 3 1 1 5
2 1 5 3 7
5 2 6 3 2
5 2 6 3 8
6 1 1 1 1

Czyli każda kolumna posortowana tak, aby zachować sortowanie poprzedniej kolumny.
Żeby było kolorowiej nie mogę użyć containerów STL gdyby miały się gdzieś tu przydać.

Posortowałem całą listę według data1, i co dalej? Gdybym teraz zaczął sortować według data2, to całe sortowanie według data1 by się popsuło.

Oczywiście googlowałem, dużo propozycji pojawiało się o std::stable_sort, ale wszystkie przykłady były pokazane na np. vectorach. Kompletnie nie wiem jak to przełożyć na "customowe" struktury danych. Coś też o lambda znalazłem, ale to koncepcyjnie jeszcze trudniejsze dla mnie, nigdy wcześniej tego nie używałem.

Macie jakieś propozycje lub przykłady jak to zaimplementować?

3

Trochę to oszukane, ale chyba Ci uzna. Sortuj całymi rzędami za pomocą std::tie: https://dsp.krzaq.cc/post/245/jak-latwo-zaimplementowac-w-cxx-operator-porownania-dla-twojej-klasy/

0

@kq:
Usunąłem data4 i data5 z problemu dla uproszczenia.

bool operator<(Node const& l, Node const& r)
{
    return std::tie(l.data1, l.data2,  l.data3) <
           std::tie(r.data1, r.data2,  r.data3);
}

Czym tak właściwie jest l, a czym r?
W moim wypadku l to head a r to head->next?

1

Tak (pomijając fakt, że l i r to referencje, a nie wskaźniki w moim przykładzie). Zamiast jakiegoś if(current->data1 < current->next->data1) wywołaj tę funkcję: if(*current < *current->next)

Nie musisz jej też nazywać operator< koniecznie, tylko wtedy musisz skorzystać z normalnej konwencji wywołania funkcji.

0

Ta porównywarka CompareTwoNodes działa zbyt pięknie żeby było prawdziwe :P

#include <iostream>
#include <tuple>

struct Node {
    Node * next;
    int data1;
    int data2;
    int data3;
};

void AddToList(Node *&head, const int &data1, const int &data2, const int &data3)
{
    head = new Node {head, data1, data2, data3};
}

void PrintList(Node * head)
{
    std::cout << "Zawartosc:\n";
    while (head) {
        std::cout << head->data1 << head->data2 << head->data3 << '\n';
        head = head->next;
    }
}

bool operator<(Node const& l, Node const& r)
{
    return std::tie(l.data1, l.data2,  l.data3) <
           std::tie(r.data1, r.data2,  r.data3);
}

void CompareTwoNodes(Node *&head)
{
    auto p1 = head; // current
    auto p2 = head->next; // next
    std::cout << "\nPorownanie:\n";
    while (p2 != nullptr) {
        if (*p1 < *p2) {
            std::cout << p1->data1 << p1->data2 << p1->data3 << " < "
                      << p2->data1 << p2->data2 << p2->data3 << '\n';
        }
        else {
            std::cout << p1->data1 << p1->data2 << p1->data3 << " >= "
                      << p2->data1 << p2->data2 << p2->data3 << '\n';
        }
        p1 = p2;
        p2 = p2->next;
    }
}
int main()
{
    Node *ptr = nullptr;
    AddToList(ptr, 2, 9, 9);
    AddToList(ptr, 2, 9, 9);
    AddToList(ptr, 3, 0, 0);
    AddToList(ptr, 2, 1, 1);
    AddToList(ptr, 1, 5, 5);
    AddToList(ptr, 5, 3, 2);
    AddToList(ptr, 5, 3, 8);
    PrintList(ptr);
    CompareTwoNodes(ptr);
    return 0;
}
  1. Czy cały ten bool operator działa tak, że w całym programie w każdym miejscu gdzie używam < zamiast zwykłego defaultowego porównania, to jest wywoływane to customowe porównanie?

1.1 Jeśli tak, to czy da się ograniczyć "zasięg" działania tego customowego bool operatora?

Pytam o to, bo co w sytuacji gdy cały program jest bardzo złożony - czy mi to może gdzieś narobić szkody?

Zrobiłem proste porównanie np.:

if (5 < 555)
     std::cout << "Dziala dobrze";

i działało dobrze, ale może czegoś tu nie dostrzegam.

  1. Skoro ta porównywarka faktycznie działa, to wystarczy teraz dowolnym algorytmem sortującym (działającym dla listy jednokierunkowej) posortować tylko raz tę listę? Czy gdzieś tu jest haczyk o którym nie wiem.
2
  1. Zazwyczaj operatory dla danego typu definiuje się w nagłówkach w których zdefiniowany jest ten typ(logiczne). Czyli będzie wszędzie tam, gdzie załączysz nagłówek.
  2. No tak. Co w tym dziwnego? :P
0

A czy da się zrobić customową nazwę operatora? Np. <<< zamiast <?

bool operator <<<(Node const& l, Node const& r)
{
    // content
}

Chciałbym być móc wstanie wyraźnie określać czy chcę skorzystać z defaultowego operatora <, czy z tego customowego.

1

Myślę, że nie do końca czaisz o co chodzi. Zasadniczo to nie ma czegoś takiego jak defaultowy operator(kompilator może wygenerować tylko przypisanie).
Dopóki samemu takich operatorów nie zdefiniujesz to ich po prostu nie będzie. Dla typów wbudowanych jak int, float, itp. te operatory są... wbudowane :) Ale już np. taki std::string musi sam definiować operatory, żeby móc używać tej klasy intuicyjnie. operatory std::string.

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