오늘은 연결리스트에 대해 알아보자.
연결 리스트
연결 리스트에서는 원소들이 링크 (link) 라고 부르는 고리로 연결되어 있다.
앞서 살펴본 선형 배열보다 가운데에 있는 원소를 삭제 또는 삽입하는 것을 더 빠른 시간내로 처리하는 것이 가능하다.
이러한 이점 때문에, 원소의 삽입/삭제가 빈번히 일어나는 응용에서는 연결 리스트가 많이 이용된다.
실제 현실에서 스마트 폰 홈버튼을 두 번 누르면 나오는 앱 리스트 같은 경우 이중 연결 리스트 등으로 구현할 수 있을 것이다.
파이썬 클래스로 연결리스트 만들어보기
다음과 같이 클래스로 연결리스트를 구현해 볼 수 있다.
노드 구현하기
class Node:
def __init__(self, item):
self.data = item
self.next = None
연결 리스트 구현하기
class LinkedList:
def __init__(self):
self.nodeCount = 0
self.head = None
self.tail = None
연결 리스트의 연산들을 구현하기
연결리스트에서 사용되는 연산들을 구현해보자.
특정 위치에 있는 노드를 반환하기
#연결 리스트의 index는 1부터 시작하도록 만들었다.
def getAt(self, pos):
if pos <=0 or pos > self.nodeCount:
return None
i = 1
curr = self.head
while i < pos:
curr = curr.next
i += 1
return curr
연결 리스트에 담겨 있는 item 들을 리스트로 반환하기
def traverse(self):
lst_items = []
curr = self.head
while (curr != None):
lst_items.append(curr.data)
curr = curr.next
return lst_items
원소를 삽입하기
- 주의
삽입하려는 위치가 가장 앞이거나 가장 끝일 때
head 와 tail 조정 유의
# pos 는 1에서 nodeCount까지의 정수를 의미한다.
# 삽입에 성공하면 True를, 실패하면 False를 반환한다.
def insertAt(self, pos, newNode):
if pos < 1 or pos > self.nodeCOunt + 1:
return False
if pos = 1:
newNode.next = self.head
self.head = newNode
else:
#이전 노드를 찾는다.
if pos == self.nodeCount + 1:
prev = self.tail
else:
prev = self.getAt(pos - 1)
newNode.next = prev.next
prev.next = newNode
if pos == self.nodeCount + 1:
self.tail = newNode
self.nodeCount += 1
return True
원소를 삭제하기
- 주의
삭제하려는 node가 맨 앞이거나 맨 끝일 때
노드가 하나만 있을 때
def popAt(self, pos):
if pos < 1 or pos > self.nodeCount:
raise IndexError
if pos == 1:
data = self.head.data
if self.nodeCount == 1:
self.head = None
self.tail = None
else:
self.head = self.head.next
else :
prev = self.getAt(pos-1)
data = prev.next.data
if pos == self.nodeCount:
self.tail = prev
prev.next = None
else:
prev.next = prev.next.next
self.nodeCount -= 1
return data
연결리스트 두개를 합치기
def concat(self, L):
self.tail.next = L.head
if L.tail:
self.tail = L.tail
self.nodeCount += L.nodeCount
연산들이 효율적인가?
지금까지 구현한 코드는 getAt이라는 O(n)의 시간 복잡도를 지닌 연산을 이용한다.
때문에 매번 삽입 삭제에서 O(n)의 시간 복잡도를 가질 수 밖에 없다.
앞에 더미 노드를 지니고 있는 연결 리스트
다음 코드로 앞서 구현한 것과는 조금 다른 앞에 더미노드가 추가된 형태의 연결리스트를 구현해보자.
더미 노드를 추가하면 쓰이지 않는 노드가 하나 생기지만 데이터를 담고 있는 모든 노드의 연결 구성이 같아지므로 코드를 좀 더 효율적으로 줄일 수 있게 되는 것을 볼 수 있다.
class LinkedList:
def __init__(self):
self.nodeCount = 0
self.head = Node(None)
self.tail = None
self.head.next = self.tail
리스트 순회
def traverse(self):
lst_items = []
curr = self.head
while curr.next:
curr = curr.next
lst_items.append(curr.data)
return lst_items
k 번째 원소를 return
#연결 리스트의 index는 0부터 시작할 수 있고 head는 더미노드를 가리킨다.
def getAt(self, pos):
if pos <0 or pos > self.nodeCount:
return None
i = 0
curr = self.head
while i < pos:
curr = curr.next
i += 1
return curr
특정 노드 뒤에 새로운 노드를 삽입하는 연산
def insertAfter(self, prev, newNode):
newNode.next = prev.next
if prev.next is None:
self.tail = newNode
prev.next = newNode
self.nodeCount += 1
return True
특정 위치에 노드 삽입하기
insertAfter를 이용해서 insertAt 연산을 더 쉽게 만들 수 있다.
def insertAt(self, pos, newNode):
if pos < 1 or pos > self.nodeCount + 1:
return False
# 가장 앞과 뒤에 노드를 추가하는 방식이 더 간단해졌다!
if pos != 1 and pos == self.nodeCount + 1:
prev = self.tail
else:
prev = self.getAt(pos-1)
return self.insertAfter(prev, newNode)
원소의 삭제
- 주의
prev가 마지막 node 일 때 -> None을 리턴하자
리스트 맨 끝 노드를 삭제할 때 -> tail 조정
def popAfter(self, prev):
if prev.next == None:
return None
data = prev.next.data
if prev.next.next == None:
self.tail = prev
prev.next = prev.next.next
self.nodeCount -= 1
return data
def popAt(self, pos):
if pos < 1 or pos > self.nodeCount:
raise IndexError
prev = self.getAt(pos-1)
return self.popAfter(prev)
두 리스트의 연결
def concat(self, L):
self.tail.next = L.head.next
if L.tail:
self.tail = L.tail
self.nodeCount += L.nodeCount
연결 리스트의 전체 코드
class Node:
def __init__(self, item):
self.data = item
self.next = None
class LinkedList:
def __init__(self):
self.nodeCount = 0
self.head = Node(None)
self.tail = None
self.head.next = self.tail
def traverse(self):
result = []
curr = self.head
while curr.next:
curr = curr.next
result.append(curr.data)
return result
def getAt(self, pos):
if pos < 0 or pos > self.nodeCount:
return None
i = 0
curr = self.head
while i < pos:
curr = curr.next
i += 1
return curr
def insertAfter(self, prev, newNode):
newNode.next = prev.next
if prev.next is None:
self.tail = newNode
prev.next = newNode
self.nodeCount += 1
return True
def insertAt(self, pos, newNode):
if pos < 1 or pos > self.nodeCount + 1:
return False
if pos != 1 and pos == self.nodeCount + 1:
prev = self.tail
else:
prev = self.getAt(pos - 1)
return self.insertAfter(prev, newNode)
def popAfter(self, prev):
if prev.next == None:
return None
data = prev.next.data
if prev.next.next == None:
self.tail = prev
prev.next = prev.next.next
self.nodeCount -= 1
return data
def popAt(self, pos):
if pos < 1 or pos > self.nodeCount:
raise IndexError
prev = self.getAt(pos-1)
return self.popAfter(prev)
댓글남기기