1. 연결 리스트(Linked List)란?
연결 리스트(Linked list)는 데이터를 저장하는 선형 자료구조로써, 각 구성 요소가 노드(Node)로 구성되어 있으며 하나의 노드(Node)에는 데이터가 저장된 부분과 다음 노드를 가리키는 포인터(Pointer)로 구성되어 있습니다.
또한 연결 리스트의 시작지점을 가리키는 'Head'와 연결 리스트의 끝을 가리키는 Tail로 구성되어 있습니다.
2. 연결 리스트(Linked list)의 종류
연결 리스트의 종류는 크게 (1)Singly Linked List, (2)Doubly Linked List, (3)Circular Linked List가 있습니다. 이번 포스트에서는 Singly Linked List와 Doubly Linked List에 대해서만 다뤄보고자 합니다.
3. 단일 연결 리스트(Singly Linked List)
Singly Linked List(단일 연결 리스트)는 위 그림처럼 단방향으로 연결되어 있다는 점이 특징입니다. 즉, 하나의 노드를 보면 다음으로 연결된 노드가 무엇인지는 알 수 있지만, 어디서부터 연결되었는지에 대한 정보는 없습니다. 그렇기 때문에 시작 지점을 가리키는 Head Pointer가 꼭 필요합니다. 반대로 Tail은 노드가 가리키는게 아무것도 없는 경우가 Tail이기 때문에 단일 열결 리스트에서 Tail은 반드시 구현해야 하는 사항은 아닙니다.
Singly Linked List를 Python으로 구현하기 전에, 구현해야 하는 기능을 생각해보면 다음과 같습니다.
1. 노드를 구현하기 : 연결 리스트의 구성 요소인 노드(Node)를 구현해야 합니다. Node에는 데이터를 저장하는 부분과, 다음 노드를 가리키는 정보가 포함되어야 합니다.
2. 연결 리스트 구현하기 - 삽입 연산 구현하기 : 데이터가 새로 들어오는 경우, 새로운 데이터를 Head 부분에 추가하는 경우, Tail 부분에 추가하는 경우, 그리고 노드 중간에 추가하는 경우를 생각해서 구현해야 합니다.
3. 연결 리스트 구현하기 - 삭제 연산 구현하기 : Head를 삭제하는 경우, Tail을 삭제하는 경우, 그리고 중간에 있는 노드를 삭제하는 경우를 생각해서 구현해야 합니다.
4. 탐색 구현하기 : 원하는 자료가 연결 리스트에 있는지 탐색하는 기능을 추가합니다. 연결 리스트의 특성 상, Head를 이용해서 탐색해야 합니다.
3.1. Python을 이용한 Singly Linked List 구현 - Node 구현하기
class Node:
def __init__(self,data):
self.data = data
self.pointer = None
Node의 구성은 데이터와 포인터로 구성되어 있으므로, 이 부분을 구현하였습니다.
3.2. Python을 이용한 Singly Linked List 구현
Singly Linked List의 삽입과 삭제 연산을 구현하기 전에, 몇가지 간단한 함수를 구현하였습니다.
- is_empty(self) : is_empty 함수는 Singly Linked List에 저장된 데이터가 있는지 없는지를 알려주는 함수입니다. 1개 이상의 원소가 있다면 True, 없다면 False를 return 합니다.
- search(self,data) : search 함수는 찾고자 하는 data를 입력받게 되면, 해당 data가 포함된 node가 있는지를 찾는 함수입니다. 만약, 찾고자 하는 data를 포함한 node가 있다면 True, 없다면 False를 return 합니다.
- display(self) : display 함수는 현재 Singly Linked List에 포함된 노드의 연결 관계를 보여줍니다. 연결 관계를 보기 좋게 표현하기 위해 시작 지점(start)과 끝 지점(end)을 표현하였으며, 연결 순서를 표현하기 위해 '->' 기호를 사용하였습니다.
예시는 다음과 같습니다.
class SinglyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def is_empty(self):
if self.head:
return False
else:
return True
def search(self,data):
start = self.head
while start:
node_data = start.data
if data == node_data:
return start
start = start.pointer
else:
return False
def display(self):
start = self.head
print('start ->',end ='')
while start:
print(' ',start.data,' ',end = '->')
start = start.pointer
else:
print(' end')
다음으로는 삽입 연산을 구현하였으며, 크게 3종류로 구성하였습니다.
- add_first(self) : add_first 함수는 Singly Linked List에서 Head가 가리키는 부분에 원소를 추가하는 함수입니다. 즉, 제일 첫 번째 부분에 원소를 추가합니다. 원소를 추가하면서 고려해야 하는 부분은 새롭게 삽입되는 노드의 pointer를 기존 Head가 가리키고 있는 노드로 설정해줘야 한다는 점 입니다. 또한 삽입 후에는 Head가 가리키는 노드는 새롭게 추가된 노드로 설정해야 합니다. 만약, Singly Linked List에 원소가 아무것도 없는 경우에는 Head와 Tail 모두를 새롭게 추가된 노드를 가리키게 합니다.
- add_last(self) : add_last 함수는 Singly Linked List에서 Tail이 가리키는 부분에 원소를 추가하는 함수입니다. 즉, 제일 마지막 부분에 원소를 추가합니다. 제일 마지막 부분에 원소를 추가하면서 고려해야 하는 부분은 기존에 Tail이 가리키고 있는 노드의 pointer를 새롭게 추가한 노드로 변경해 주어야 한다는 점 입니다. 또한 새롭게 추가된 노드는 제일 끝에 위치하게 되므로, Tail이 가리키는 노드는 새롭게 추가된 노드를 가리켜야 한다는 점 입니다. 추가로, 만약 tail이 없는 경우, Singly Linked List에는 데이터가 없다는 의미 이므로, add_first 함수를 실행합니다.
- add_data(self,node_data,new_data) : add_data 함수는 Singly Linked List에서 특정 데이터 다음에 추가시키는 함수입니다. 즉, 아래 그림처럼 1 -> 3 순서로 데이터가 입력된 Singly Linked List가 있을 때, 이 둘 사이에 2라는 데이터를 입력하는 경우 사용하는 함수입니다. 이런 경우, '어느 위치(node_data)에 어떤 데이터(new_data)를 입력하는지'에 대한 정보가 반드시 필요하므로, 해당 정보를 입력 받고 Head부터 순차적으로 입력 기준이 되는 노드를 탐색하면서, 해당 노드를 발견하게 되면 새롭게 입력한 노드를 pointer로 가리켜주어야 하며, 새롭게 입력한 노드에 대해서는 기존 노드가 가리키고 있던 노드를 가리켜 주어야 합니다.
위 내용을 Python code로 작성하면 다음과 같습니다.
class SinglyLinkedList:
def __init__(self):
self.head = None
self.tail = None
...
def add_first(self,data):
node = Node(data)
if self.head:
node.pointer = self.head
else:
self.tail = node
self.head = node
def add_last(self,data):
node = Node(data)
if self.tail:
self.tail.pointer = node
self.tail = node
else:
self.add_first(data)
def add_data(self,node_data,new_data):
new_node = Node(new_data)
start = self.head
while start:
target = start.data
if target == node_data:
new_node.pointer = start.pointer
start.pointer = new_node
break
start = start.pointer
else:
return False
위 코드를 이용하여 삽입 연산을 수행하면 다음과 같습니다.
또한 0 다음으로 -1 을 추가하는 add_data 함수를 실행해보았습니다.
마지막으로, 삭제 연산을 구현해야 합니다. 삭제 연산은 삽입 연산과 마찬가지로 3종류로 구현하였습니다.
- remove_first(self) : remove_first 함수는 Head 포인터가 가리키는 첫 번째 노드를 삭제하는 함수입니다. 삭제 시 고려 해야 하는 부분은 Head 포인터가 가리키는 부분이 삭제되므로, Head 포인터가 가리키는 노드를 두 번째 노드로 바꿔주는 작업을 수행해야 합니다. 또한, Singly Linked List 안에 노드가 단 1개인 경우, Head와 Tail이 가리켜야 하는 노드가 없으므로 None으로 설정해야 하는 작업을 추가해야 합니다.
- remove_last(self) : remove_last 함수는 Tail 포인터가 가리키는 마지막 노드를 삭제하는 함수입니다. add_last(self) 함수와는 다르게, 마지막 노드에는 이전 노드가 어떤 노드인지에 대한 정보가 없기 때문에 Head 포인터를 이용해서 바로 이전의 노드가 무엇인지 찾아야 합니다. 따라서 반복문을 이용하여 마지막 바로 이전의 노드를 탐색하고, 해당 노드의 포인터를 None으로 설정합니다. 마지막으로 Tail의 포인터를 해당 노드로 바꿔줘야 합니다. 이전 remove_first와 마찬가지로, Singly Linked List에 노드가 단 1개인 경우는 remove_first 함수에서 했던 작업과 동일한 작업을 수행합니다.
- remove_data(self,data) : remove_data 함수는 Singly Linked List에 포함되어 있는 노드를 삭제하는 함수입니다. Head 포인터를 이용하여 입력받은 데이터와 같은 데이터를 찾게 되면, 해당 노드가 가리키고 있던 포인터는 이전 노드의 포인터로 바꿔주는 작업을 수행합니다. 만약 입력받은 데이터가 Singly Linked List에 없다면, 순회만 하고 아무 작업을 하지 않습니다.
class SinglyLinkedList:
def __init__(self):
self.head = None
self.tail = None
...
def remove_first(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.head
self.head = old_node.pointer
def remove_last(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
start = self.head
while start.pointer != self.tail:
start = start.pointer
start.pointer = None
self.tail = start
def remove_data(self,data):
start = self.head
if self.head.data == data:
remove_first()
return
elif self.tail.data == data:
remove_last()
else:
while start.pointer:
next_data = start.pointer.data
if next_data == data:
start.pointer = start.pointer.pointer
break
start = start.pointer
else:
return
위 코드를 이용하여 이전 상태에서 삭제를 진행해보겠습니다.
start -> 1 -> 2 -> 3 -> 0 -> -1 -> 10 -> 20 -> 30 -> end
위 상태에서 remove_first, remove_last, remove_data(0)를 실행하면 다음과 같습니다.
정상적으로 작동이 됨을 확인 할 수 있습니다. 마지막으로, 원소가 한개도 없을 때 까지 remove_data를 실행해보면 다음과 같습니다.
마지막으로 Singly Linked List에 노드가 있는지 판단하는 is_empty 함수를 실행시켜 보았을 때 True를 출력하는 모습까지 확인 할 수 있습니다.
Singly Linked List의 구현을 마치면서, 구현한 전체 코드는 다음과 같습니다.
class SinglyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def is_empty(self):
if self.head:
return False
else:
return True
def search(self,data):
start = self.head
while start:
node_data = start.data
if data == node_data:
return start
start = start.pointer
else:
return False
def add_first(self,data):
node = Node(data)
if self.head:
node.pointer = self.head
else:
self.tail = node
self.head = node
def add_last(self,data):
node = Node(data)
if self.tail:
self.tail.pointer = node
self.tail = node
else:
self.add_first(data)
def add_data(self,node_data,new_data):
new_node = Node(new_data)
start = self.head
while start:
target = start.data
if target == node_data:
new_node.pointer = start.pointer
start.pointer = new_node
break
start = start.pointer
else:
return False
def remove_first(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.head
self.head = old_node.pointer
def remove_last(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
start = self.head
while start.pointer != self.tail:
start = start.pointer
start.pointer = None
self.tail = start
def remove_data(self,data):
start = self.head
if self.head.data == data:
remove_first()
return
elif self.tail.data == data:
remove_last()
else:
while start.pointer:
next_data = start.pointer.data
if next_data == data:
start.pointer = start.pointer.pointer
break
start = start.pointer
else:
return
def display(self):
start = self.head
print('start ->',end ='')
while start:
print(' ',start.data,' ',end = '->')
start = start.pointer
else:
print(' end')
4. 이중 연결 리스트(Doubly Linked List)
이중 연결 리스트(Doubly Linked List)는 단일 연결 리스트(Singly Linked List)와 거의 비슷하지만, 이전 노드에 대한 정보가 담겨있는 포인터가 추가된다는 차이점이 있습니다. 그림으로 살펴보면 다음과 같습니다.
그림에서 보면, 하나의 노드에서 포인터가 하나 추가되었으며, 추가된 포인터는 이전 노드를 가리키고 있습니다. 따라서 구현 시, Node 클래스에 포인터를 하나 추가시켜주어야 합니다.
class Node:
def __init__(self,data):
self.data = data
self.prev_pointer = None
self.next_pointer = None
다음으로 Doubly Linked List의 구현을 살펴보겠습니다. Singly Linked List의 코드를 거의 대부분 재사용하므로 자세한 설명은 생략하며, 삽입 및 삭제에서 바뀌는 부분만 살펴보겠습니다.
4.1 Python을 이용한 Doubly Linked List 구현
삽입 부분에서 바뀌는 부분은 prev_pointer에 대한 구현을 추가로 진행해야 합니다. add_first 와 add_last에서 추가된 노드에 대한 prev_pointer 정보를 추가하고, 기존에 있던 prev_node와 next_node에 대한 정보도 업데이트 해 줍니다. 또한, add_data 함수는 총 4개의 포인터(새롭게 추가된 노드의 포인터 2개 + 기존에 연결되어 있던 포인터 2개)에 대해 연결 관계를 업데이트 해 주어야 합니다.
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
...
def add_first(self,data):
node = Node(data)
if self.head:
node.next_pointer = self.head
self.head.prev_pointer = node
else:
self.tail = node
self.head = node
def add_last(self,data):
node = Node(data)
if self.tail:
node.prev_pointer = self.tail
self.tail.next_pointer = node
self.tail = node
else:
self.add_first(data)
def add_data(self,node_data,new_data):
new_node = Node(new_data)
start = self.head
while start:
target = start.data
if target == node_data:
new_node.next_pointer = start.next_pointer
new_node.prev_pointer = start
start.next_pointer.prev_pointer = new_node
start.next_pointer = new_node
break
start = start.pointer
else:
return False
위 코드를 이용하여 이전 Singly Linked List와 같은 예시로 실행해 보았습니다. 실행에 앞서, display함수도 살짝 변경하였습니다.
...
def display(self):
start = self.head
print('start ->',end ='')
while start:
print(' ',start.data,' ',end = '->')
start = start.next_pointer
else:
print(' end')
print('end <-',end ='')
end = self.tail
while end:
print(' ',end.data,' ',end = '<-')
end = end.prev_pointer
else:
print(' start')
각 포인터들의 연결관계가 잘 되었는지 확인하기 위해, Head 포인터부터 시작하는 부분에 더해 Tail 포인터부터 시작하는 부분을 추가하여 출력하도록 했습니다.
다음으로는 삭제 연산 부분입니다. 삭제 연산도 삽입 연산과 마찬가지로 prev_pointer의 정보를 업데이트하는 부분을 추가하였습니다.
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
...
def remove_first(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.head
self.head = old_node.next_pointer
self.head.prev_pointer = None
def remove_last(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.tail
self.tail = old_node.prev_pointer
self.tail.next_pointer = None
def remove_data(self,data):
start = self.head
if self.head.data == data:
remove_first()
return
elif self.tail.data == data:
remove_last()
else:
while start.next_pointer:
node_data = start.data
if node_data == data:
start.prev_pointer.next_pointer = start.next_pointer
start.next_pointer.prev_pointer = start.prev_pointer
break
start = start.next_pointer
else:
return
위 코드를 이용하여 이전에 추가한 노드를 삭제해보겠습니다.
마지막으로 노드가 1개도 없을 때 까지 삭제도 해보았습니다.
Singly Linked List와 마찬가지로 정상적으로 삽입 및 삭제가 작동함을 확인할 수 있습니다.
마지막으로, Doubly Linked List를 구현한 전체 코드 입니다.
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def is_empty(self):
if self.head:
return False
else:
return True
def search(self,data):
start = self.head
while start:
node_data = start.data
if data == node_data:
return start
start = start.next_pointer
else:
return False
def display(self):
start = self.head
print('start ->',end ='')
while start:
print(' ',start.data,' ',end = '->')
start = start.next_pointer
else:
print(' end')
print('end <-',end ='')
end = self.tail
while end:
print(' ',end.data,' ',end = '<-')
end = end.prev_pointer
else:
print(' start')
def add_first(self,data):
node = Node(data)
if self.head:
node.next_pointer = self.head
self.head.prev_pointer = node
else:
self.tail = node
self.head = node
def add_last(self,data):
node = Node(data)
if self.tail:
node.prev_pointer = self.tail
self.tail.next_pointer = node
self.tail = node
else:
self.add_first(data)
def add_data(self,node_data,new_data):
new_node = Node(new_data)
start = self.head
while start:
target = start.data
if target == node_data:
new_node.next_pointer = start.next_pointer
new_node.prev_pointer = start
start.next_pointer.prev_pointer = new_node
start.next_pointer = new_node
break
start = start.pointer
else:
return False
def remove_first(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.head
self.head = old_node.next_pointer
self.head.prev_pointer = None
def remove_last(self):
if self.head == self.tail:
self.head = None
self.tail = None
return
old_node = self.tail
self.tail = old_node.prev_pointer
self.tail.next_pointer = None
def remove_data(self,data):
start = self.head
if self.head.data == data:
remove_first()
return
elif self.tail.data == data:
remove_last()
else:
while start.next_pointer:
node_data = start.data
if node_data == data:
start.prev_pointer.next_pointer = start.next_pointer
start.next_pointer.prev_pointer = start.prev_pointer
break
start = start.next_pointer
else:
return
'파이썬(Python) > 자료구조(Data Structure)' 카테고리의 다른 글
[자료구조] 트리(Tree)와 트리 순환(Tree Traversal) 정리 (0) | 2024.11.12 |
---|---|
[자료구조] 힙(Heap) 정리 (0) | 2024.11.08 |
[자료구조] 우선순위 큐(Priority Queue) 정리 (0) | 2024.11.07 |
[자료구조] 재귀(Recursion) 정리 (0) | 2024.02.16 |
[Python] Stack(스택) 과 Queue(큐) 정리 및 예제 (0) | 2022.08.23 |