반응형
Preface
이번 장에선 연결 리스트의 단점을 보완한 원형 이중 연결 리스트를 공부했다.
코드의 구성 면에선 포인터와 커서를 이용한 연결 리스트와 크게 다른 부분이 없어 손쉽게 코드를 작성할 수 있었다.
나는 왜 지금껏 연결 리스트를 공부하며 노드를 역순으로 스캔할 생각을 하지 않았을까?
코드의 효율성은 단순히 얻어지는 것이 아니라 끝없이 더 나은 방법을 모색해야만 떠올릴 수 있는 것인데, 나는 책에 나온 방법에 한해서만 고민하는 것 같다.
아마도 '우물 안의 개구리'는 이럴 때 쓰는 말이 아닐까 싶다.
더 넓게 더 멀리 봐야 하는데 주어진 내용이 전부라 생각하고 그 이상은 생각하지 않게 된다.
공부를 하면 할 수록 책에 나온 내용만을 공부하는 것이 아닌, 개인 프로젝트를 통해 나만의 코드를 작성하며 이런 저런 방법을 고민해보는 것이 정말 중요한 것 같다는 생각이 든다.
- 원형 리스트 : 연결 리스트의 꼬리 노드가 다시 머리 노드를 가리키는 구조
- 연결 리스트의 단점 : 앞쪽 노드를 찾기 어려움
- 이중 연결 리스트 : 각 노드에 뒤쪽 노드의 포인터뿐만 아니라 앞쪽 노드의 포인터도 주어지는 구조
→ 연결 리스트의 단점을 개선한 것이다.
- 원형 이중 연결 리스트 : 원형 리스트와 이중 연결 리스트를 결합한 구조
- 원형 이중 연결 리스트 코드
# 원형 이중 연결 리스트 구현하기
from __future__ import annotations
from typing import Any, Type
class Node: # 원형 이중 연결 리스트용 노드 클래스
def __init__(self, data: Any = None, prev: Node = None,
next: Node = None) -> None:
self.data = data
self.prev = prev or self
self.next = next or self
# 파이썬 논리 연산자 참고
class DoubleLinkedList:
def __init__(self) -> None:
self.head = self.current = Node()
self.no = 0
def __len__(self) -> int:
return self.no
def is_empty(self) -> bool: # 더미 노드를 참조하므로 비어있는 것
return self.head.next is self.head
def search(self, data: Any) -> Any: # 맨 앞에 더미 노드가 있으므로 다음 노드부터 검색
cnt = 0
ptr = self.head.next
while ptr is not self.head:
if data == ptr.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 print_current_node(self) -> None:
if self.is_empty():
print('주목 노드는 없습니다.')
else:
print(self.current.data)
def print(self) -> None:
ptr = self.head.next
while ptr is not self.head:
print(ptr.data)
ptr = ptr.next
def print_reverse(self) -> None:
ptr = self.head.prev
while ptr is not self.head:
print(ptr.data)
ptr = ptr.prev
def next(self) -> bool:
if self.is_empty() or self.current.next is self.head:
return False
self.current = self.current.next
return True
def prev(self) -> bool:
if self.is_empty() or self.current.prev is not self.head:
return False
self.current = self.current.prev
return True
def add(self, data: Any) -> None:
node = Node(data, self.current, self.current.next)
self.current.next.prev = node
self.current.next = node
self.current = node
self.no += 1
def add_first(self, data: Any) -> None:
self.current = self.head
self.add(data)
def add_last(self, data: Any) -> None:
self.current = self.head.prev
self.add(data)
def remove_current_node(self) -> None:
if not self.is_empty():
self.current.prev.next = self.current.next
self.current.next.prev = self.current.prev
self.current = self.current.prev
self.no -= 1
if self.current is self.head:
self.current = self.head.next
def remove(self, p: Node) -> None:
ptr = self.head.next
while ptr is not self.head:
if ptr is p:
self.current = p
self.remove_current_node()
break
ptr = ptr.next
def remove_first(self) -> None:
self.current = self.head.next
self.remove_current_node()
def remove_last(self) -> None:
self.current = self.head.prev
self.remove_current_node()
def clear(self) -> None:
while not self.is_empty():
self.remove_first()
self.no = 0
def __iter__(self) -> DoubleLinkedListIterator:
return DoubleLinkedListIterator(self.head)
def __reversed__(self) -> DoubleLinkedListReverseIterator:
return DoubleLinkedListReverseIterator(self.head)
class DoubleLinkedListIterator:
def __init__(self, head: Node):
self.head = head
self.current = head.next
def __iter__(self) -> DoubleLinkedListIterator:
return self
def __next__(self) -> Any:
if self.current is self.head:
raise StopIteration
else:
data = self.current.data
self.current = self.current.next
return data
class DoubleLinkedListReverseIterator:
def __init__(self, head: Node):
self.head = head
self.current = head.prev
def __iter__(self) -> DoubleLinkedListReverseIterator:
return self
def __next__(self) -> Any:
if self.current is self.head:
raise StopIteration
else:
data = self.current.data
self.current = self.current.prev
return data
※ 파이썬에서 '='은 연산자가 아니라 변수에 대한 단순 참조를 나타낼 뿐이다.
- 원형 이중 연결 리스트 프로그램 코드
from enum import Enum
from double_list import DoubleLinkedList
Menu = Enum('Menu', ['머리에노드삽입', '꼬리에노드삽입', '주목노드바로뒤삽입',
'머리노드삭제', '꼬리노드삭제', '주목노드출력',
'주목노드이동', '주목노드역순이동', '주목노드삭제',
'모든노드삭제', '검색', '멤버십판단', '모든노드출력',
'모든노드역순출력', '모든노드스캔', '모든노드역순스캔', '종료'])
def select_menu() -> Menu:
s = [f'({m.value}){m.name}' for m in Menu]
while True:
print(*s, sep=' ', end='')
n = int(input(': '))
if 1 <= n <= len(Menu):
return Menu(n)
lst = DoubleLinkedList()
while True:
menu = select_menu()
if menu == Menu.머리에노드삽입:
lst.add_first(int(input('머리 노드에 넣을 값을 입력하세요.: ')))
elif menu == Menu.꼬리에노드삽입:
lst.add_last(int(input('꼬리 노드에 넣을 값을 입력하세요.: ')))
elif menu == Menu.주목노드바로뒤삽입:
lst.add(int(input('주목 노드 바로 뒤에 넣을 값을 입력하세요.: ')))
elif menu == Menu.머리노드삭제:
lst.remove_first()
elif menu == Menu.꼬리노드삭제:
lst.remove_last()
elif menu == Menu.주목노드출력:
lst.print_current_node()
elif menu == Menu.주목노드이동:
lst.next()
elif menu == Menu.주목노드역순이동:
lst.prev()
elif menu == Menu.주목노드삭제:
lst.remove_current_node()
elif menu == Menu.모든노드삭제:
lst.clear()
elif menu == Menu.검색:
pos = lst.search(int(input('검색할 값을 입력하세요.: ')))
if pos >= 0:
print(f'이 키를 갖는 데이터는 {pos + 1}번째에 있습니다.')
else:
print('해당 데이터가 없습니다.')
elif menu == Menu.멤버십판단:
print('그 값의 데잉터는 포함되어'
+ (' 있습니다.' if int(input('판단할 값을 입력하세요.: ')) in lst
else '있지 않습니다.'))
elif menu == Menu.모든노드출력:
lst.print()
elif menu == Menu.모든노드역순출력:
lst.print_reverse()
elif menu == Menu.모든노드스캔:
for e in lst:
print(e)
elif menu == Menu.모든노드역순스캔:
for e in reversed(lst):
print(e)
else:
break
728x90
반응형
'Algorithm > 자료구조와 함께 배우는 알고리즘(Python)' 카테고리의 다른 글
(Fin) 트리 구조, 이진 트리와 이진 검색 트리 (0) | 2022.01.21 |
---|---|
커서를 이용한 연결 리스트 (0) | 2022.01.15 |
연결 리스트, 포인터를 이용한 연결 리스트 (1) | 2022.01.13 |
브루트 포스법, KMP법, 보이어·무어법 (0) | 2022.01.12 |
힙 정렬, 도수 정렬 (0) | 2022.01.10 |
댓글