Zrozumienie implementacji połączonych list w Pythonie

Struktury danych stanowią fundament programowania, umożliwiając efektywną organizację i manipulację informacjami.

W tym przewodniku przyjrzymy się bliżej listom jednokierunkowym i dwukierunkowym.

Lista wiązana to liniowa struktura, w której dane nie są przechowywane w ciągłych fragmentach pamięci, jak ma to miejsce w tablicach. Każdy element, zwany węzłem, jest powiązany z kolejnym za pomocą wskaźników. Pierwszy węzeł w liście to jej głowa.

Rozmiar listy wiązanej jest dynamiczny, co oznacza, że możemy dodawać węzły do momentu, aż dostępna pamięć zostanie wyczerpana.

Istnieją dwa główne rodzaje list wiązanych. Przeanalizujmy je szczegółowo.

# 1. Lista Jednokierunkowa

Lista jednokierunkowa charakteryzuje się tym, że każdy węzeł zawiera wskaźnik do następnego elementu. Przechowujemy zatem dane oraz odnośnik do kolejnego węzła.

Ostatni węzeł ma wskaźnik ustawiony na wartość null, co sygnalizuje koniec listy.

Poniżej znajduje się ilustracja przedstawiająca strukturę listy jednokierunkowej.

Po takim wprowadzeniu, przejdźmy do implementacji listy jednokierunkowej w Pythonie.

Implementacja Listy Jednokierunkowej

1. Pierwszym etapem jest stworzenie reprezentacji węzła.

Jak to zrobić?

W Pythonie możemy wygodnie zdefiniować węzeł za pomocą klasy. Klasa ta będzie przechowywać dane oraz wskaźnik do kolejnego węzła.

Oto kod implementujący węzeł:

class Node:

	def __init__(self, data):
		## dane przechowywane w węźle
		self.data = data

		## wskaźnik do kolejnego węzła
		self.next = None

Wykorzystując tę klasę, możemy tworzyć węzły przechowujące dane dowolnego typu. Zobaczymy to w dalszej części.

Mając zdefiniowany węzeł, możemy przejść do stworzenia samej listy. W tym celu utworzymy kolejną klasę.

2. Stwórzmy klasę `LinkedList`, w której głowa (pierwszy węzeł) zostanie zainicjowana jako `None`. Oto kod:

class LinkedList:

	def __init__(self):
		## inicjalizacja głowy jako None
		self.head = None

3. Posiadając klasy `Node` i `LinkedList`, jak wstawić nowy węzeł do listy? Najprościej będzie dodać do klasy `LinkedList` metodę wstawiającą. Zaimplementujmy ją.

Wstawianie nowego węzła wymaga wykonania kilku kroków:

Spójrzmy na nie:

  • Sprawdź, czy lista jest pusta (czy głowa ma wartość None).
  • Jeśli lista jest pusta, przypisz nowy węzeł do głowy.
  • Jeśli lista nie jest pusta, znajdź ostatni węzeł.
  • Ustaw wskaźnik ostatniego węzła na nowy węzeł.

Oto kod, który implementuje te kroki:

class LinkedList:

	####

	def insert(self, new_node):
		## sprawdzenie, czy głowa ma wartość None
		if self.head:
			## znalezienie ostatniego węzła
			last_node = self.head
			while last_node.next != None:
				last_node = last_node.next

			## przypisanie nowego węzła do wskaźnika następnego węzła
			last_node.next = new_node

		else:
			## lista jest pusta
			## przypisanie nowego węzła do głowy
			self.head = new_node

Świetnie! Metoda wstawiania nowego węzła została ukończona. Jak możemy uzyskać dostęp do danych przechowywanych w węzłach?

Aby tego dokonać, musimy przejść przez listę, iterując po wskaźnikach `next`. Zastosujemy podobne podejście jak przy szukaniu ostatniego węzła. Napiszmy metodę, która wyświetli dane wszystkich węzłów.

4. Oto kroki niezbędne do wyświetlenia danych listy:

  • Zainicjuj zmienną z głową.
  • Wykorzystaj pętlę, która będzie iterować dopóki nie dotrze do końca listy.
    • Wyświetl dane bieżącego węzła.
    • Przejdź do następnego węzła.

Spójrzmy na kod:

class LinkedList:

	####

	def display(self):
		## zmienna do iteracji
		temp_node = self.head

		## iterowanie dopóki nie dotrzemy do końca listy
		while temp_node != None:
			## wyświetlanie danych węzła
			print(temp_node.data, end='->')

			## przejście do kolejnego węzła
			temp_node = temp_node.next

		print('Null')

Udało się! Mamy podstawową implementację listy jednokierunkowej z niezbędnymi metodami. Przetestujmy ją, tworząc instancję z danymi.

Węzeł możemy utworzyć za pomocą kodu `Node(1)`. Spójrzmy na pełny kod implementacji, wraz z przykładem użycia:

class Node:

	def __init__(self, data):
		## dane przechowywane w węźle
		self.data = data

		## wskaźnik do kolejnego węzła
		self.next = None

class LinkedList:

	def __init__(self):
		## inicjalizacja głowy jako None
		self.head = None

	def insert(self, new_node):
		## sprawdzenie, czy głowa ma wartość None
		if self.head:
			## znalezienie ostatniego węzła
			last_node = self.head
			while last_node.next != None:
				last_node = last_node.next

			## przypisanie nowego węzła do wskaźnika następnego węzła
			last_node.next = new_node

		else:
			## lista jest pusta
			## przypisanie nowego węzła do głowy
			self.head = new_node

	def display(self):
		## zmienna do iteracji
		temp_node = self.head

		## iterowanie dopóki nie dotrzemy do końca listy
		while temp_node != None:
			## wyświetlanie danych węzła
			print(temp_node.data, end='->')

			## przejście do kolejnego węzła
			temp_node = temp_node.next

		print('Null')


if __name__ == '__main__':
	## utworzenie instancji listy
	linked_list = LinkedList()

	## dodanie danych do listy
	linked_list.insert(Node(1))
	linked_list.insert(Node(2))
	linked_list.insert(Node(3))
	linked_list.insert(Node(4))
	linked_list.insert(Node(5))
	linked_list.insert(Node(6))
	linked_list.insert(Node(7))

	## wyświetlenie listy
	linked_list.display()

Uruchomienie powyższego programu da następujący wynik:

1->2->3->4->5->6->7->Null

To wszystko w kontekście list jednokierunkowych. Teraz już wiesz jak je zaimplementować. Mając tę wiedzę, możesz łatwo poradzić sobie z implementacją listy dwukierunkowej. Przejdźmy do kolejnej części poradnika.

#2. Lista Dwukierunkowa

Lista dwukierunkowa, w przeciwieństwie do jednokierunkowej, ma w każdym węźle dwa wskaźniki: jeden do poprzedniego węzła, a drugi do następnego. Oznacza to, że każdy węzeł zawiera dane, wskaźnik do poprzednika oraz wskaźnik do następnika.

Poprzedni wskaźnik pierwszego węzła i następny wskaźnik ostatniego węzła mają wartość null, co oznacza odpowiednio początek i koniec listy.

Poniżej znajduje się ilustracja struktury listy dwukierunkowej.

Implementacja listy dwukierunkowej jest podobna do jednokierunkowej. Powtarzanie tych samych kroków byłoby nużące, więc przejdźmy od razu do kodu, który z pewnością zrozumiesz bardzo szybko. Zaczynajmy.

Implementacja Listy Dwukierunkowej

1. Definiujemy węzeł listy dwukierunkowej, zawierający wskaźnik do poprzedniego węzła, dane oraz wskaźnik do następnego węzła.

class Node:

	def __init__(self, data):
		## wskaźnik do poprzedniego węzła
		self.prev = None

		## dane przechowywane w węźle
		self.data = data

		## wskaźnik do kolejnego węzła
		self.next = None

2. Klasa reprezentująca listę dwukierunkową.

class LinkedList:

	def __init__(self):
		## inicjalizacja głowy jako None
		self.head = None

3. Metoda wstawiania nowego węzła do listy dwukierunkowej.

class LinkedList:

	####

	def insert(self, new_node):
		## sprawdzenie, czy głowa ma wartość None
		if self.head:
			## znalezienie ostatniego węzła
			last_node = self.head
			while last_node.next != None:
				last_node = last_node.next

			## przypisanie ostatniego węzła do wskaźnika poprzedniego węzła nowego węzła
			new_node.prev = last_node

			## przypisanie nowego węzła do wskaźnika next ostatniego węzła
			last_node.next = new_node

4. Metoda wyświetlania danych listy dwukierunkowej.

class LinkedList:

	####

	def display(self):

		## wyświetlanie w normalnej kolejności
		print("Normal Order: ", end='')

		temp_node = self.head
		while temp_node != None:
			print(temp_node.data, end=' ')
			temp_node = temp_node.next
		print()

		## wyświetlanie w odwrotnej kolejności z użyciem wskaźnika do poprzedniego węzła
		print("Reverse Order: ", end='')

		## znalezienie ostatniego węzła
		last_node = self.head
		while last_node.next != None:
			last_node = last_node.next

		temp_node = last_node
		while temp_node != None:
			print(temp_node.data, end=' ')
			temp_node = temp_node.prev
		print()

Widzieliśmy kod implementujący listę dwukierunkową. Nie zamieszczono jednak kodu, który pozwoliłby na praktyczne przetestowanie tej klasy. Zostawiam to jako zadanie dla Ciebie. Użyj klasy `LinkedList` w podobny sposób jak wcześniej, przy liście jednokierunkowej. Powodzenia!

Podsumowanie

Istnieje wiele problemów, które można rozwiązać za pomocą list wiązanych. Grunt to zrozumieć podstawową implementację, którą przedstawiliśmy w tym samouczku. Mam nadzieję, że nauka nowej koncepcji sprawiła Ci przyjemność.

Miłego kodowania! 🙂


newsblog.pl