연결 리스트
리스트
- 데이터에 순서를 매겨 늘어놓은 자료구조
- 구조가 단순한 리스트로 선형 리스트 또는 연결 리스트가 있음
- 스택과 큐도 리스트 자료구조에 속함
- 파이썬의 리스트 자료형과는 다름
연결 리스트
- 노드
- 연결 리스트의 각각의 원소
- 데이터와 뒤쪽 노드를 참조하는 포인터로 구성되어 있음
- 머리노드 : 연결리스트 맨 앞에 있는 원소
- 꼬리노드 : 연결리스트 맨 뒤에 있는 노드
- 앞쪽노드 : 어떤 노드의 바로 앞에 있는 노드
- 뒤쪽노드 : 어떤 노드의 바로 뒤에 있는 노드
배열로 연결 리스트 만들기
- 어떤 그룹의 회원 정보 데이터를 나타내는 튜플
- int형 회원번호, str형 이름, str형 전화번호
- list형 배열인 data의 원소는 7개, data[5:]는 등록되지 않은 상태
- 뒤쪽 노드 꺼내기
- 인덱스 값을 1씩 증가시켜 원소에 접근
- 노드의 삽입과 삭제
- 회원번호가 55인 회원을 추가하려면 추가한 원소 이후의 모든 원소가 하나씩 이동해야함
- 삭제하는 경우에도 배열 안의 일부 원소를 모두 이동해야함
- 데이터를 삽입, 삭제함에 따라 데이터를 옮겨야 해서 효율적이지 않음
포인터를 이용한 연결 리스트
- 데이터를 삽입할 때 노드용 인스턴스를 생성하고, 데이터를 삭제할 때 노드용 인스턴스를 소멸
- Node 클래스
- 데이터용 필드 data와 별도로 자신과 같은 클래스형의 인스턴스를 참조하기 위한 참조용 필드 next로 구성(data : 데이터에 대한 참조, next : 노드에 대한 참조(꼬리노드의 next는 None))
- 자기 참조형 구조 : 자신과 같은 형의 인스턴스를 참조하는 필드가 있는 구조
노드 클래스
- 필드
- data : 데이터
- next : 뒤쪽 포인터
파이썬의 리스트 자료형
- 연결 리스트는 임의의 위치에 원소를 삽입하거나 삭제할 때 빠르게 수행할 수 있다는 장점, 메모리와 속도에 대해서는 배열보다 효율이 떨어짐
- 파이썬의 리스트 자료형은 연결 리스트의 자료구조가 아님
- 모든 원소를 메모리에 연속적으로 배치하는 배열로 내부적으로 구현되어 있음
from __future__ import annotations
from typing import Any, Type
class Node:
def __init__(self, data = None, next = None):
self.data = data
self.next = next
class LinkedList:
def __init__(self) -> None:
self.no = 0
self.head = None
self.current = None
def __len__(self) -> int:
return self.no
def search(self, data:Any) -> int:
cnt = 0
ptr = self.head
while ptr is not None:
if ptr.data == data:
self.current = ptr
return cnt
cnt += 1
ptr = ptr.next
return -1
def __contains__(self, data : Any) -> bool:
return self.search(data) >= 0
def add_first(self, data : Any) -> None:
ptr = self.head
self.head = self.current = Node(data, ptr)
self.no += 1
def add_last(self, data : Any):
if self.head is None:
self.add_first(data)
else:
ptr = self.head
while ptr.next is not None:
ptr = ptr.next
ptr.next = self.current = Node(data, None)
self.no += 1
def remove_first(self) -> None:
if self.head is not None:
self.head = self.current = self.head.next
self.no -= 1
def remove_last(self):
if self.head is not None:
if self.head.next is None:
self.remove_first()
else:
ptr = self.head
while ptr.next is not None:
pre = ptr
ptr = ptr.next
pre.next = None
self.current = pre
self.no -= 1
def remove(self, p : None) -> None:
if self.head is not None:
if p is self.head():
self.remove_first()
else:
ptr = self.head
while ptr.next is not p:
ptr = ptr.next
if ptr is None:
return
ptr.next = p.next
self.current = ptr
self.no -= 1
def remoce_current_node(self) -> None:
self.remove(self.current)
def clear(self) -> None:
while self.head is not None:
self.remove_first()
self.current = None
self.no = 0
def next(self) -> bool:
if self.current is None or self.current.next is None:
return False
else:
self.current = self.current.next
return True
def print_current_node(self) -> None:
if self.current is None:
print("주목 노드 존재 X")
else:
print(self.current.data)
def print(self) -> None:
ptr = self.head
while ptr is not None:
print(ptr.data)
ptr = ptr.next
def __iter__(self) -> LinkedListIterator:
return LinkedListIterator(self.head)
class LinkedListIterator:
def __init__(self, head : Node):
self.current = head
def __iter__(self) -> LinkedListIterator:
return self
def __next__(self) -> Any:
if self.current is None:
raise StopIteration
else:
data = self.current.data
self.current = self.current.next
return data
연결 리스트의 길이 구하기
- 빈 연결 리스트
- 연결 리스트가 비어있을 때 head값은 None
- 노드가 1개인 연결 리스트
- 헤드가 참조하는 노드 A는 머리노드이자 꼬리노드
- head가 참조하는 뒤쪽 포인터의 값이 헤드
- 노드가 2개인 연결 리스트
- head가 머리 노드 A를 가리키며, 노드 A의 뒤쪽 포인터 넥스트가 꼬리 노드 B를 가리킴
- 꼬리 노드 여부 판단
- Node형인 변수 p가 리스트에 있는 노드를 참조할 때, p의 next 필드가 None이면 p는 꼬리노드
이터러블 객체와 이터레이터의 구현
- 이터러블 객체
- 원소를 1개씩 꺼내는 구조의 객체
- str형 문자열, list형 리스트, tuple형 튜플
커서를 이용한 연결리스트
포인터를 이용한 연결리스트의 한계
- 포인터를 이용한 연결리스트는 노드를 삽입, 삭제할 때 데이터를 이동하지 않고 처리
- 노드를 삽입, 삭제할 때마다 내부에서 노드용 인스턴스를 생성하고 소멸함
- 메모리를 확보하고 해제하는데 많은 비용이 소모
커서를 이용한 연결리스트
- 프로그램 실행 중 데이터 개수가 크게 변하지 않거나 데이터 최대 개수를 예측할 수 있는 경우엔 배열 안의 원소를 사용하여 효율적으로 운용함
- 커서
- int형 정숫값인 인덱스로 나타낸 포인터
- 노드 B의 뒤쪽 커서 3은 뒤쪽 노드가 인덱스 3의 위치에 있는 노드 C라는 의미
- 노드의 삽입과 삭제에 따른 원소의 이동이 처음부터 불필요함
- 맨앞에 노드 G를 삽입하는 과정
- 배열 안에서 가장 끝쪽에 있는 인덱스 위치에 저장
- 연결 리스트 상에서 맨 끝이 아님
- 배열에서 물리적인 이치 관계와 연결 리스트의 논리적인 순서 관계가 일치하지 않음
- head를 1에서 6으로 업데이트 하고 노드 G의 뒤쪽 커서를 1로 설정함
삭제된 노드 관리
- 배열 안에서 가장 끝쪽에 있는 인덱스 위치에 저장
- 연결 리스트에서 A, B, C, D로 연결된 상태
- 맨 앞에 새로운 노드 E삽입
- 인덱스 4의 위치에 노드 E 저장(4번째 레코드)
- 3번째 레코드 노드 B를 삭제
- 삭제를 여러번 하면 배열 안에 빈 레코드가 많이 생김
### 프리 리스트
- 삭제된 레코드 그룹을 관리할 때 사용하는 자료구조
- 원래 데이터의 순서를 관리하는 연결리스트와 결합하여 구현
- 노드 클래스 Node에 추가된 필드
- dnext : 프리 리스트의 뒤쪽 노드를 참조하는 커서
- 연결 리스트 ArrayLinkedList에 추가된 필드
- deleted : 프리 리스트의 머리 노드를 참조하는 커서
- max : 배열에서 맨 끝 쪽에 저장되는 레코드 번호
- 연결 리스트에 원소 삽입 시, 프리 리스트가 비어있지 않으면 프리 리스트의 머리 노드에 원소를 저장하고, 비어 있으면 배열에서 맨 끝에 저장
- 연결 리스트에서 노드 삭제 시, 삭제한 레코드 인덱스를 프리 리스트의 머리 노드로 등록
파이썬의 논리 연산자
- 많은 프로그래밍 언어에서 논리 연산자는 평가 결과로 True, False 등의 불리언 값을 생성
- 파이썬의 논리 연산자는 다른 프로그래밍 언어와 다르게 동작함
- x and y : x를 평가하여 거짓이면 그 값을 생성, 그렇지 않으면 y를 평가하여 그 값을 생성
- x or y : x를 평가하여 참이면 그 값을 생성, 그렇지 않으면 y를 평가하여 그 값을 생성
- not x : x가 참이면 False, 거짓이면 True 생성
- and연산자와 or연산자가 생성하는 값은 가장 마지막에 평가한 식의 결과값
- 5 or 3의 평가 결과값은 5
- 0 or 3의 평가 결과값은 3
- and연산자와 or연산자는 논리 연산의 평가 결과가 왼쪽 피연산자의 평가 결과만으로 확정되는 경우, 오른쪽 피연산자의 평가를 생략하는 단축 평가를 수행
- and연산자와 or연산자가 생성하는 값은 가장 마지막에 평가한 식의 결과값
원형리스트
- 연결리스트의 꼬리 노드가 다시 머리 노드를 가리키는 모양
- 고리 모양으로 늘어선 데이터를 표현하는데 알맞은 자료구조
- 원형 리스트의 꼬리 노드의 뒤쪽 포인터가 None이 아닌 머리 노드의 포인터 값이 됨
이중 연결 리스트
- 연결 리스트의 가장 큰 단점은 뒤쪽 노드를 찾기 쉬운 반면, 앞쪽 노드를 찾기 어렵다는 것
- 이중 연결 리스트의 각 노드는 뒤쪽 노드에 대한 포인터와 앞쪽 노드에 대한 포인터가 주어짐
- data : 데이터 참조
- prev : 앞쪽 노드 참조
- next : 뒤쪽 노드 참조
원형 이중 연결 리스트
- 원형리스트와 이중 연결 리스트를 결합
- 원형 이중 연결 리스트에서 개별 노드의 형은 이중 연결 리스트와 동일
self.prev = prev or self self.next = next or self
- prev 또는 next로 전달받은 인자가 None이 아닌(참)경우 prev 또는 next를 대입하여 참조
- prev 또는 next로 전달받은 인자가 None(거짓)인 경우 self를 대입하여 자신의 인스턴스 참조
add() : 주목 노드 바로 뒤에 노드를 삽입하는 매서드
- 연결 리스트와 달리 더미 노드가 있어, 빈 리스트에 삽입이나 리스트 맨 앞에 삽입 처리하는 과정 불필요 add_first() : 머리에 노드를 삽입하는 경우 add_last() : 꼬리에 노드를 삽입하는 경우
reversed()
- 앞쪽으로 스캔하기 위한 이터레이터 반환
self.current.next = self.current.next.prev = node
- C언어, 자바등에서 ‘=’은 대입 연산자로 오른쪽 결합 연산자(정상 동작)
- self.current.next.prev = node
- self.current.next = self.current.next.prev
- 파이썬에서 ‘=’은 연산자가 아닌 대입 명령문으로 연속 대입의 경우 다음과 같이 동잦(오동작)
- self.current.next = node
- self.current.next.prev = node