Listy jednokierunkowe, dwukierunkowe, cykliczne

PL
Data dodania: 2011-09-18, Autor: david357, Dodał: Karol, Wyświetleń: 1300

Dynamiczne struktury

Stworzenie dynamicznej struktury danych sprowadza się do stworzenia:

  • struktury zawierającej wskaźnik(i) na samą siebie, oraz zmienne wskazywane
  • funkcje narzędziowe zapewniające możliwość tworzenia i przetwarzania dynamicznych struktur

Stosy i kolejki są to implementacje zbiorów dynamicznych, w których dostęp mamy do jednoznacznie określonych elementów (pierwszy i,lub ostatni).

Listą nazywamy liniowo uporządkowany zbiór składników, z którego w dowolnym miejscu można usunąć składnik, jak również dołączyć nowy. W zależności od typu wiązań między składnikami wyróżniamy listy: jednokierunkowe, dwukierunkowe, cykliczne.

Listy jednokierunkowe:

Organizacja listy jednokierunkowej podobna jest do stosu, tzn. dla każdego składnika (poza ostatnim) jest określony składnik następny lub dla każdego składnika (poza pierwszym) jest określony składnik poprzedni.

Struktura:

typedef struct Element_listy{
   typ_danych dane;
   struct Element_listy *wskaznik;
} T_lista;

Dane są dowolnego typu, natomiast zmienna HEAD jest wskaźnikiem na składnik listy i przechowuje adres pierwszego elementu (gdy lista jest pusta przechowuje NULL).

Funkcje:

T_lista Znajdź(T_lista *HEAD, typ_danych x)  
/*funkcja szuka elementu x w liście na którą wskazuje HEAD, zwraca jego adres 
albo null gdy lista nie zawiera elementu x.*/
{
   T_lista *temp;
   temp=HEAD;
   while ( temp!=NULL && temp->dane!=x)
      temp=temp->wskaznik;
   return temp;
}

Pesymistyczny czas działania tej funkcji na liście n-elementowej wynosi teta(n), ponieważ niekiedy konieczne jest przejście całej listy.

void Wstaw(T_lista *BIEZACY,typ_danych x)
/*funkcja wstawia element x do listy za elementem na który wskazuje 
wskaźnik BIEZACY*/
{
   T_lista *nowy=(T_lista *)malloc(sizeof(T_lista));
   nowy->dane=x;                           //1
   nowy->wskaznik=BIEZACY->wskaznik;       //2
   BIEZACY->wskaznik=nowy;                 //3
}

W podobny sposób można napisać funkcję wstawiającą element na pozycji przed pierwszym składnikiem listy:

void Wstaw(T_lista **HEAD, typ_danych x)
/*funkcja wstawia element x do listy na pozycji przed pierwszym składnikiem*/
{
   T_lista nowy=(T_lista *)malloc(sizeof(T_lista));
   nowy->dane=x;
   nowy->wskaznik=*HEAD;
   *HEAD=nowy;
}

Obydwie funkcje wstawiania działają w czasie O(1)

void Usuń(T_lista **HEAD, typ_danych x)
/*funkcja usuwa składnik x z listy na którą wskazuje wskaźnik HEAD*/
{
   T_lista *temp;
   T_lista *poprzedni;
   temp=*HEAD;
   while(temp!=NULL && temp-> dane!=x)
      poprzedni=temp;
      temp=temp->wskaznik;
   if(temp==*HEAD) *HEAD=*HEAD->wskaznik;
   else poprzedni->wskaznik=temp->wskaznik;
   if (temp!=NULL) free(temp);
}

Listy dwukierunkowe:

Listą dwukierunkową nazywamy liniowo uporządkowany zbiór składników, w którym dla każdego składnika, poza pierwszym i ostatnim, określony jest składnik poprzedni i następny.

Struktura:

typedef struct Element_listy{
   typ_danych dane;
   struct Element_listy *wskaznik1;
   struct Element_listy *wskaznik2;
} T_lista;

Funkcje:

Wyszukiwanie elementu x w listach dwu- i jednokierunkowych odbywa się w ten sam sposób, natomiast wstawianie i usuwanie elementów jest łatwiejsze.

void Wstaw(T_lista *BIEZACY, typ_danych x)
/*funkcja wstawia element x do listy za elementem na 
który wskazuje wskaźnik BIEZACY*/
{
   T_lista *nowy=(T_lista *)malloc(sizeof(T_lista));
   nowy->dane=x;
   nowy->wskaznik1=BIEZACY->wskaznik1;   //2
   nowy->wskaznik2=BIEZACY;              //3
   BIEZACY->wskaznik1=nowy;              //4
   nowy->wskaznik1->wskaznik2=nowy;      //5
}

W podobny sposób można napisać funkcję wstawiającą element na pozycji przed pierwszym składnikiem listy:

void Wstaw(T_lista **HEAD,typ_danych x)
/*funkcja wstawia element x do listy na pozycji przed pierwszym składnikiem*/
{
   T_lista *nowy=(T_lista *)malloc(sizeof(T_lista));
   nowy->dane=x;
   nowy->wskaznik1=*HEAD;
   nowy->wskaznik2=NULL;
   *HEAD=nowy;
}
void Usuń(T_lista **HEAD,typ_danych x)
/*funkcja usuwa składnik x z listy na którą wskazuje wskaźnik HEAD*/
{
   T_lista *temp;
   temp=HEAD;
   while(temp!=NULL && temp->dane!=x)
      temp=temp->wskaznik1;
   if(temp==HEAD)
   {
      HEAD=HEAD->wskaznik1;
      temp->wskaznk1->wskaznik2=NULL;
   }
   else if (temp!=NULL) 
   {
      temp->wskaznik2->wskaznik1=temp->wskaznik1;   //6
      temp->wskaznik1->wskaznik2=temp->wskaznik2;   //7
      free(temp);
   }
   
}

Listy cykliczne:

Gdyby można było pominąć warunki brzegowe dotyczące głowy i ogona listy, funkcje obsługujące listę znacznie by się uprościły. Tę rolę odgrywa wartownik, sztuczny element który pozwala uprościć warunki brzegowe. Wartownik zastępuje stałą NULL w listach jednokierunkowych i dwukierunkowych (wszędzie gdzie w listach występowała stała NULL wstawiamy wskaźnik na wartownika). W listach dwukierunkowych wartownik zawiera wskaźniki na głowę i ogon listy.

Struktura:

typedef struct Element_listy{
   typ_danych dane;
   struct Element_listy *next;
   struct Element_listy *prev;
} T_lista;

/*Listę inicjalizuje się następująco:*/
T_lista *WARTOWNIK=(T_lista *)malloc(sizeof(T_lista));
WARTOWNIK->next=WARTOWNIK;
WARTOWNIK->prev=WARTOWNIK;

Pusta lista wygląda tak:

gdzie WARTOWNIK to wskaźnik do wartownika. Zaś lista z dwoma elementami tak:

Funkcje:

T_lista *Znajdź(T_lista *WARTOWNIK, typ_danych x)
/*funkcja szuka elementu x w liście na którą wskazuje HEAD, 
zwraca jego adres albo null gdy lista nie zawiera elementu x.*/
{
   T_lista *temp;
   temp=WARTOWNIK->next;
   while (temp!=WARTOWNIK && temp->dane!=x)
      temp=temp->next;
   return temp;
}
void Wstaw(T_lista *BIEZACY, typ_danych x)
/*funkcja wstawia element x za elementem BIEZACY, 
gdy chcemy dodać element na początku listy to BIEZACY=WARTOWNIK*/
{
   T_lista *nowy=(T_lista *)malloc(sizeof(T_lista));
   nowy->next=BIEZACY->next;
   nowy->prev=BIEZACY;
   nowy->next->prev=nowy;
   BIEZACY->next=nowy;
}
void Usuń(T_lista *WARTOWNIK, T_lista *BIEZACY)
{
   If (BIEZACY!=WARTOWNIK)
   {
      BIEZACY->prev->next=BIEZACY->next;
      BIEZACY->next->prev=BIEZACY->prev;
   }
}

Użycie wartownika nie poprawia złożoności operacji na strukturze danych, ale często prowadzi do zmniejszenia stałych współczynników. Korzyść wynikająca z zastosowania wartowników w pętlach programu polega często na poprawie czytelności programu, a nie szybkości jego działania.


Bibliografia

  • T.H. Cormen, C.E. Leiserson, R.L. Rivest, Clifford Stein -
  • "Wprowadzenie do algorytmów", WNT Warszawa 2004, wyd. szóste
  • A. Marciniak – „Turbo Pascal 7.0 z elementami programowania” cz. 1, BUM Poznań 1990-1994

 


Aby dodawać komentarze musisz być zalogowany!


Kontakt

Jeśli chcesz się z nami skontaktować napisz na adres: info(at)binboy.org lub odwiedź nasz profil na Facebooku!

O Nas

Serwis binboy.org to kopalnia wiedzy dla wszystkich z branży IT, w szczególności dla programistów i webmasterów. To duży zbiór kursów programowania, tutoriali, darmowych ebooków, setki kodów źródłowych itp.

Bądź w kontakcie

Panel użytkownika

Zaloguj się do panelu użytkownika.
Nie masz konta? Zarejestruj się!
Zapomniałeś hasła?