Struktura dynamiczna: drzewo list. Wstawianie sortujące do listy

0

Witam wszystkich,
Mam problem ze swoim programem.
Założenia są takie, że program ma wczytać z pliku listę osób z przypisanym numerem lotu, numerem miejsca i danymi pasażera, a następnie utworzyć strukturę dynamiczną i ją wyświetlić. Ma to być drzewo list, w którym węzłem jest numer lotu, a w liście mają być przyporządkowani pasażerowie danego lotu, posortowani względem numeru miejsca.

Z wczytaniem danych, ze strukturą drzewa oraz z procedurą wyświetlenia węzłów i listy już sobie poradziłem. Na razie jest to rekurencja, ale zmienię to niedługo na coś lepszego ;) Problem mam jednak w czymś innym - w dodawaniu autosortującym do listy.

Program kompiluje się poprawnie, jednak wywala błąd External: SIGSEGV i Lazarus podświetla jedną z linijek (zaznaczyłem to w kodzie). Męczę się z tym już 3 dzień, ale nie umiem tego naprawić. Co jest nie tak w tej procedurze WstawDoListy?

Kod:

program loty;
uses
  Sysutils, interfejs, crt;
type
  Lot = record
        nrlotu, imie, nazwisko : String;
        nrrezerwacji, nrmiejsca, key: Integer;
  end;
  PList = ^TList;
  TList = record
          dane: lot;
          key: integer;
          next: PList;
  end;
  PNode = ^TNode;
  TNode = record
          key: string;
          wsklista: PList;
          left, right: PNode;
  end;

procedure WstawDoListy(var glowa: PList; rekord: lot);
var
nowa, tmp, prev:PList;
begin
new(nowa);
nowa^.dane:=rekord;
nowa^.next:=NIL;
while ((tmp<>NIL) or (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
begin
  prev:=tmp;
  tmp:=tmp^.next;    // <-- ta linijka jest podswietlana po wyswietleniu bledu External: SIGSEGV
end;
if (glowa = NIL ) then
  glowa:=nowa
else if (tmp=NIL) then
  prev^.next:=nowa
else if (tmp^.dane.nrmiejsca=rekord.nrmiejsca) then
  writeln('Blad w pliku: Pasazer o takim samym numerze miejsca juz istnieje')
else
  begin
    glowa:=nowa;
    nowa^.next:=tmp;
  end;
end; 

procedure WstawdoStruktury(var root: PNode; var rekord:lot);
var
element,elementnext,tmp: PList;
begin
if root = NIL then
  begin
  new(root);
  root^.key := rekord.nrlotu;
  WstawDoListy(element, rekord);
  root^.wsklista := element;
  root^.right:=NIL;
  root^.left:=NIL;
  end
else
    if root^.key = rekord.nrlotu then WstawdoListy(element, rekord);
    if root^.key < rekord.nrlotu then WstawdoStruktury(root^.left, rekord);
    if root^.key > rekord.nrlotu then WstawdoStruktury(root^.right, rekord);
end;

procedure WyswietlListe (glowa: PList);
var
tmp: PList;
begin
     tmp := glowa;
   while (tmp<>NIL) do
   begin
   writeln(tmp^.dane.nrmiejsca, ' ', tmp^.dane.nrrezerwacji, ' ', tmp^.dane.imie, ' ', tmp^.dane.nazwisko);
   tmp:= tmp^.next;
   end;
end;

procedure WyswietlDrzewo(p: PNode);
begin
if p<>NIL then
  begin
  WyswietlDrzewo(p^.left);
  Writeln('Wezel: ',p^.key);
  WyswietlListe(p^.wsklista);
  WyswietlDrzewo(p^.right);
  end;
end;

{jest to glowna procedura uruchamiajaca podrzedne tak, aby zrealizowac funkcje programu}
{procedura wczytuje dane z pliku i zapisuje je do rekordow}
procedure WczytaniezPliku(plik:string; niecmd:boolean);
var
  Rekord: lot;
  p : Text; {p - od plik}
  Linia : String;
  root: PNode;
begin
root := NIL;
if FileExists(plik) then
  begin
  AssignFile(p,plik);
  Reset(p);
  while not eof(p) do
  begin
    ReadLn(p,Linia);
    SScanf(Linia,'%s %d %d %s %s',[@Rekord.nrlotu,@Rekord.nrrezerwacji,@Rekord.nrmiejsca,@Rekord.imie,@Rekord.nazwisko]);
    WstawDoStruktury(root, rekord);
    // wpisanie do drzewa list (jezeli lot istnieje to wstaw pasazera do danego lotu)
  end;
  Close(p);
  WyswietlDrzewo(root);
  end
else Writeln('Blad: Plik "',plik,'" nie istnieje.');
if niecmd then readln;
end;  

begin
WczytaniezPliku('plik.txt',true);
end.

Zawartość pliku (dajmy na to plik.txt) wygląda tak:

FL345X 323 5 Jan Kowalski
GL2225 233 3 Adam Nowak
GPS325 263 2 Janusz Korwin-Mikke
FL345X 221 1 Michal Kowalski
GL2225 123 4 Wojciech Niski
GL2225 135 2 Elvis Presley

Byłbym ogromnie wdzięczny gdyby ktoś mi pomógł. Z góry dziękuję!

Pozdrawiam

EDIT: Poprawiłem trochę czytelność kodu

0

Ja nie ogarniam tych list różno kierunkowych i tego typu wynalazków. Ale zastanawia mnie jedno. Skoro już masz w sekcji uses moduł SysUtils, to onzacza że nie chce klepać w WinAPI i możesz wspomóc się metodami z VCL. W takim razie dodaj sobie do sekcji uses moduł Classes i wspomóż się gotową konstrukcją jaką jest klasa TList. Więcej pewnie doradzą tu inni.

1

@wjertlo - Twoim największym problemem jest brak sensownego formatowania kodu i nazewnictwo, w którym mieszasz polskie i angielskie identyfikatory; Tego się nie robi, więc albo użyj słów angielskich, albo amerykańskich - polskie identyfikatory to zło; Dodatkowo brak stylu wielbłądziego jeszcze bardziej psuje kod;


Piszesz, że ta linijka powoduje SIGSEGV:

while ((tmp <> NIL) or (tmp^.dane.nrmiejsca < rekord.nrmiejsca)) do
begin
  prev := tmp;
  tmp := tmp^.next;
end;

i na to wygląda, że tmp nie jest utworzony w pamięci - stąd problem przy próbie odwołania się do tmp.next; Sprawdź to pod debugerem jeśli chcesz być pewny w 100%.

0

@olesio, nie mogę tak zrobić. Jest to projekt na zaliczenie na studia i ma to być moja własna implementacja struktur dynamicznych. Także dzięki za propozycję, ale to odpada.
@furious programming, dziękuję bardzo za rady. Nazewnictwo zmiennych itd. poprawię na końcu (na razie morduję się z tym, żeby wstawiało to do te listy, bo to byłby jeden z ostatnich elementów do zrobienia + usunięcie rekurencji), a w kolejnym projekcie będę się starał już nie robić takich błędów.

Trochę przerobiłem te dwie procedury:

procedure WstawDoListy(var glowa: PList; var root:PNode; rekord: lot);
var
nowa, tmp,prev:PList;
begin
new(nowa);
nowa^.dane:=rekord;
nowa^.next:=NIL;
new(tmp); 
tmp := root^.wsklista;
while ((tmp<>NIL) or (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
begin
  prev:=tmp;
  tmp:=tmp^.next;
end;
if (glowa = NIL ) then
  glowa:=nowa
else if (tmp=NIL) then
  prev^.next:=nowa
else if (tmp^.dane.nrmiejsca=rekord.nrmiejsca) then
  writeln('Blad w pliku: Pasazer o takim samym numerze miejsca juz istnieje')
else
  begin
    glowa:=nowa;
    nowa^.next:=tmp;
  end;
end;

procedure WstawdoStruktury(var root: PNode; var rekord:lot);
var
element,elementnext,tmp: PList;
begin
if root = NIL then
  begin
  new(root);
  root^.key := rekord.nrlotu;
  root^.wsklista := NIL;
  WstawDoListy(element, root, rekord);
  root^.wsklista := element;
  root^.right:=NIL;
  root^.left:=NIL;
  end
else
    if root^.key = rekord.nrlotu then WstawdoListy(element, root, rekord);
    if root^.key < rekord.nrlotu then WstawdoStruktury(root^.left, rekord);
    if root^.key > rekord.nrlotu then WstawdoStruktury(root^.right, rekord);
end; 

Niestety, dalej występuje ten sam błąd w tej samej linijce. Wydaje mi się, że powinno być wszystko w porządku (w końcu tworzę w pamięci wskaźnik tmp i kopiuję do niego pierwszy element listy, a od tego elementu ma zacząć wyszukać odpowiednie miejsce na liście), ale jednak nie jest. Czy źle to rozumiem? Można prosić o jakieś wskazówki?

1
while ((tmp <> NIL) or (tmp^.dane.nrmiejsca < rekord.nrmiejsca)) do

bo ma być and

0

Kurczę, faktycznie. Próbowałem to ugryźć z wielu stron, ale nie pomyślałem o tak oczywistej zmianie. Dzięki wielkie @_13th_Dragon.
Błąd niestety nadal występuje, ale trochę dalej.

Aktualnie procedura przedstawia się tak:

procedure WstawDoListy(var glowa: PList; var root:PNode; rekord: lot);
var
nowa, tmp,prev:PList;
begin
new(nowa);
nowa^.dane:=rekord;
nowa^.next:=NIL;
tmp := root^.wsklista;
while ((tmp<>NIL) AND (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
begin
  prev:=tmp;
  tmp:=tmp^.next;
end;
if glowa = NIL  then
  glowa:=nowa
else if tmp = NIL then
prev^.next:=nowa // BŁĄD SIGSEVG
else if (tmp^.dane.nrmiejsca=rekord.nrmiejsca) then
  writeln('Blad w pliku: Pasazer o takim samym numerze miejsca juz istnieje')
else
  begin
    glowa:=nowa;
    nowa^.next:=tmp;
  end;
end; 

Słuchajcie, to nie tak, że ja tutaj liczę na gotowe rozwiązania i nie chce mi się ruszyć głową - sam już trochę spędziłem czasu nad tym programem i nie lecę z byle błahostką na forum - dopiero wtedy, gdy utknę na dobre, a próbowałem wielu rozwiązań i żadne z nich nie działa. Przyznacie, że temat ze strukturami dynamicznymi i operowaniem na wskaźnikach jest trochę ciężki, czasem poruszam się po tych rejonach po omacku - dzięki nauce na błędach jednak wiele się uczę, także dziękuję za wszelkie wskazówki i naprowadzenie na dobre tory ;)

1
wjertlo napisał(a)

Słuchajcie, to nie tak, że ja tutaj liczę na gotowe rozwiązania i nie chce mi się ruszyć głową - sam już trochę spędziłem czasu nad tym programem i nie lecę z byle błahostką na forum - dopiero wtedy, gdy utknę na dobre, a próbowałem wielu rozwiązań i żadne z nich nie działa.

Ty chyba za długo siedzisz nad tym programem, bo naprawdę prostych błędów nie widzisz :]

Spójrz jeszcze raz na kod tej procedury i zobacz co się stanie, jeśli wskaźnik tmp będzie wskazywał na nil:

tmp := root^.wsklista;
while ((tmp<>NIL) AND (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
begin
  prev:=tmp;
  tmp:=tmp^.next;
end;
if glowa = NIL  then
  glowa:=nowa
else if tmp = NIL then
prev^.next:=nowa // BŁĄD SIGSEVG

Najważniejsza pierwsza i ostatnia linia tego kawałka kodu; Jeśli tmp będzie wskazywał na nil, pętla w ogóle się nie wykona, a tym samym wskaźnik prev nie zostanie zainicjowany; Przez to przy próbie dostania się do jego pola next dostaniesz błąd naruszenia pamięci; Zmodyfikuj więc kod tak, aby nie było możliwości używać niezainicjowanych wskaźników.

0

Postanowiłem dziś, że napiszę całkowicie od nowa tę procedurę - sam się w niej gubiłem. Obecnie wygląda tak i... wszystko działa! Mam jednak prośbę: możecie rzucić okiem, czy wszystko jest w miarę poprawnie napisane? Chodzi mi o to, żeby nie było jakichś wycieków pamięci czy błędów - choć wydaje mi się, że rozpatrzyłem wszystkie przypadki.

procedure WstawDoListy(var glowa: PList; var root:PNode; rekord: lot);
var
nowa, tmp,prev:PList;
begin
new(nowa);
nowa^.dane:=rekord;
nowa^.next:=NIL;
tmp := root^.wsklista;
prev := NIL;
while ((tmp<>NIL) AND (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
begin
  prev:=tmp;
  tmp:=tmp^.next;
end;
if (prev = NIL) AND (tmp = NIL) then  //przypadek 1: brak elementow na liscie
  begin
       glowa := nowa;
       root^.wsklista := glowa;
  end;
if (prev = NIL) AND (tmp <> NIL) then // przypadek 2: wstawianie na samym poczatku listy
  begin
       glowa := nowa;
       root^.wsklista := glowa;
       glowa^.next := tmp;
  end;
if (prev <> NIL) AND (tmp = NIL) then //przypadek 3: wstawianie na samym koncu listy
  begin
       glowa := nowa;
       prev^.next := glowa;
       glowa^.next := NIL;
  end;
if (prev <> NIL) AND (tmp <> NIL) then //przypadek 4: wstawianie w srodku listy
  begin
      glowa := nowa;
      prev^.next := glowa;
      glowa^.next := tmp;
  end;
if (tmp <> NIL) AND (tmp^.dane.nrmiejsca=rekord.nrmiejsca) then //przypadek 5: dwoch pasazerow o tym samym miejscu
  writeln('Blad w pliku: Pasazer o takim samym numerze miejsca juz istnieje')
end;

procedure WstawdoStruktury(var root: PNode; var rekord:lot);
var
element,tmp: PList;
begin
if root = NIL then
  begin
  new(root);
  root^.key := rekord.nrlotu;
  root^.wsklista := NIL;
  WstawDoListy(element, root, rekord);
  root^.right:=NIL;
  root^.left:=NIL;
  end
else
    if root^.key = rekord.nrlotu then WstawdoListy(element, root, rekord);
    if root^.key < rekord.nrlotu then WstawdoStruktury(root^.left, rekord);
    if root^.key > rekord.nrlotu then WstawdoStruktury(root^.right, rekord);
end;  

Dzięki za pomoc!

1

A tak to powinno wyglądać:

procedure WstawDoListy(var root:PNode;const rekord:lot);
var pos:^PList;
var nowa:PList;
begin
  pos:=@(root^.wsklista);
  while (pos^<>nil)and(pos^^.dane.nrmiejsca<rekord.nrmiejsca) do pos:=@(pos^^.next);
  new(nowa);
  nowa^.dane:=rekord;
  nowa^.next:=pos^;
  pos^:=nowa;
end;
0

Szacun, @_13th_Dragon, ale to już wyższa szkoła jazdy :P Nie mam takiego doświadczenia i takich bogatych umiejętności, żeby wpaść na takie zaawansowane rozwiązania. Nie chcę i nie będę kopiować Twojej wersji procedury, bo chcę, by cały projekt był w 100% mój ;)
Mam pytanie: czy rozpatrywanie poszczególnych przypadków w moim kodzie czyni go dużo mniej efektywnym niż Twój kod? Wiem, że może wygląda dość amatorsko, ale czy to dużo zmienia?

2

If jest dosyć kosztowną operacją. Wywal tą głowa bo jest niepotrzebna. Pogrupuj if'y i zostanie ci tylko jeden o ile zauważysz że glowa^.next := NIL; tylko gdy tmp=nil

0

Okej, dzięki za pomoc, sporo ruszyłem z projektem do przodu. Aktualnie ta procedurka wygląda tak:

procedure WstawDoListy(var root:PNode; rekord: lot; langArray: array of string);
var
nowa, tmp,prev:PList;
begin
tmp := root^.wsklista;
prev := NIL;
  while ((tmp<>NIL) AND (tmp^.dane.nrmiejsca<rekord.nrmiejsca)) do
    begin
      prev:=tmp;
      tmp:=tmp^.next;
    end;
  if (prev = NIL) then // przypadek 1 i 2: brak elementow na liscie lub wstawianie na samym poczatku listy
     root^.wsklista := NowyElement(rekord, tmp)
  else //przypadek 3 i 4: wstawianie na samym koncu listy lub wstawianie w srodku listy
      prev^.next := NowyElement(rekord, tmp);
  if (tmp <> NIL) AND (tmp^.dane.nrmiejsca=rekord.nrmiejsca) then //przypadek 5: dwoch pasazerow o tym samym miejscu
  writeln(langArray[3]);
end; 

Na pewno dałoby się coś jeszcze zoptymalizować, ale tu już nie będę grzebał. Założmy, że to mam zrobione.

Napotkałem na niestety kolejny problem - próbuję zwolnić pamięć, usunąć całkowicie moją strukturę drzewa list. Tak wygląda aktualnie kod:

procedure ZwolnijPamiec(var root: PNode);
var
tmp: PList;
begin
while ((root<>NIL) AND (root^.wsklista^.next<>NIL)) do
      begin
      tmp := root^.wsklista;
      root^.wsklista:=root^.wsklista^.next;
      dispose(tmp); // tutaj błąd SIGSEGV
      end;
ZwolnijPamiec(root^.left);
ZwolnijPamiec(root^.right);
dispose(root);
end; 

I znowu ten sam błąd - SIGSEGV przy zaznaczonej linii. Najgorsze, że nie mogę dopatrzyć się tu naruszenia pamięci - próbowałem przy while dawać tmp<>NIL i parę innych rzeczy - cały czas to samo.
Ponadto mam pytanie: w jaki sposób mogę się dowiedzieć, że ten kod faktycznie całą wolną pamięć zwalnia? Są jakieś do tego narzędzia?
Dziękuję z góry i pozdrawiam

2
procedure ZwolnijPamiec(var root: PNode);
var tmp,nxt:PList;
begin
  if root<>NIL then
  begin
    ZwolnijPamiec(root^.left);
    ZwolnijPamiec(root^.right);
    tmp:=root^.wsklista;
    while tmp do
    begin
      nxt:=tmp^.next;
      dispose(tmp);
      tmp:=nxt;
    end;
    dispose(root);
  end;
end;
0

Dziękuję bardzo za pomoc! Wszystko ładnie działa! Pozdrawiam.

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