16 분 소요

개요

연결 리스트는 데이터와 포인터가 포함된 노드들로 구성된 선형 리스트이다. 여기서 포인터는 다음 노드(또는 이전 노드) 값을 가리킴으로써 노드와 노드 사이가 연결될 수 있다. 마지막 노드는 그 뒤에 포인터가 가리킬 노드가 없으므로 해당 노드는 Null값을 가지게 된다. 파이썬에서는 None을 가진다. 기차 칸과 기차 칸이 연결된 일종의 기차와도 같다.

연결 리스트의 맨 앞 노드가 어떤 노드인지를 표시하기 위해 head라는 포인터를 사용할 수도 있다.

연결 리스트의 종류

연결 리스트에는 여러 종류가 있다.

  • 단일 연결 리스트: 각 노드에는 데이터가 들어갈 공간 하나와 한 개의 포인터가 들어갈 공간으로 이루어져있고, 각 노드의 포인터는 다음 노드를 가리키는 구조이다.

단일 연결 리스트 구조. [3] 참조.

단일 연결 리스트 구조. [3] 참조.

  • 이중 연결 리스트: 단일 연결 리스트와 거의 같으나, 차이점은 하나의 노드에 두 개의 포인터가 포함되어 있어 그 중 한 노드는 이전 노드를, 다른 노드는 다음 노드를 가리키는 구조이다. 단일 연결 리스트는 노드 A, B가 있고, A가 B를 참조하면 B는 A를 참조하지 않는 일방적인 구조를 가지나 이중 연결 리스트 구조에서는 두 노드가 서로를 참조하는 구조를 띤다.

이중 연결 리스트 구조. [3] 참조.

이중 연결 리스트 구조. [3] 참조.

  • 원형 연결 리스트: 단일 연결 리스트에서 처음 노드와 마지막 노드를 연결하여 원형을 이루게 만든 구조이다.

원형 연결 리스트 구조. [3] 참조.

원형 연결 리스트 구조. [3] 참조.

노드 삽입

단일 연결 리스트 구조로 가정하고 설명하겠다.

  • 맨 앞에 노드를 삽입하는 경우

연결 리스트가 비어 있는 경우, 즉 그 어떤 노드도 존재하지 않는 경우에는 새로 삽입할 노드를 생성한 후, head 포인터가 해당 노드를 가리키게 하면 된다.

노드의 하얀색 배경이 데이터를 담는 공간, 파란색 배경이 다른 노드를 가리키기 위한 포인터를 담는 공간이다.

노드의 하얀색 배경이 데이터를 담는 공간, 파란색 배경이 다른 노드를 가리키기 위한 포인터를 담는 공간이다.

연결 리스트가 비어있는지 확인하려면 head가 null 값을 가지는지 확인한다. null값을 가지면 연결 리스트가 비어있다는 뜻이다.

이미 기존 노드가 존재하여 연결 리스트가 비어있지 않는 경우, 일단 새 노드를 생성한 후, 새로 삽입할 노드의 포인터가 맨 앞에 있는 노드를 가리키게 하고 head 노드는 새 노드를 가리키게 하면 된다.

여기서는 이해를 쉽게 하기 위해 포인터가 가리키는 노드의 데이터 값을 포인터 공간에 담았다. 그러나 실제로는 포인터 공간에는 포인터가 가리키는 노드의 메모리 주소값을 담는다고 한다.

여기서는 이해를 쉽게 하기 위해 포인터가 가리키는 노드의 데이터 값을 포인터 공간에 담았다. 그러나 실제로는 포인터 공간에는 포인터가 가리키는 노드의 메모리 주소값을 담는다고 한다.

  • 맨 뒤에 새 노드 삽입

연결 리스트의 맨 뒤에 새 노드를 삽입하고자 한다면, 기존의 마지막 노드의 포인터가 새 노드를 가리키게 하면 된다.

연결리스트 삽입1.png

이렇게 맨 앞이나 뒤에 새 노드를 삽입하는 경우의 시간복잡도는 O(1)이다. (그저 맨 앞 또는 맨 뒤 노드를 찾아 삽입하기만 되기에) (O(1)의 시간복잡도로 맨 마지막에 노드 삽입을 위해 head 말고도 tail이란 포인터를 사용한다. 해당 포인터는 맨 마지막 노드를 가리키는 포인터로 쓰인다)

  • 중간에 노드 삽입

연결 리스트 중간의 특정 위치에 노드를 삽입하고자 한다. 기존에 노드 A, C가 있고 A → C로 연결되어 있을 때, 새 노드 B를 A와 C 사이에 삽입하고자 한다. 이 경우, B의 포인터는 A의 포인터가 가리키는 노드를 가리키도록 설정한다. 그 후, A 노드의 포인터는 새로 삽입된 B 노드를 가리키게 하면 된다.

연결리스트 삽입1.png

특정 위치에 노드를 삽입하고자 한다면 우선 그 특정 위치를 찾아야 한다. n개의 데이터가 있을 때 해당 위치를 찾을 수 있는 최악의 경우의 수는 n이다. 따라서 중간에 노드를 삽입하는 동작의 시간복잡도는 O(n)이다.

노드의 삭제 및 검색

연결리스트 삭제.png

위와 같은 연결 리스트가 존재하고, 여기서 값이 ‘c’인 노드를 삭제하고자 한다. 이를 위해서 우선 prev와 cur 포인터를 준비시켰다. 이 두 포인터는 처음에는 head 포인터가 가리키는 첫 번째 노드를 가리키게 한다. 그 다음, cur 포인터가 가리키는 노드의 값이 ‘c’인지를 조사한다. 아니라면 cur 포인터는 다음 노드를 가리키게 한다. prev 포인터는 cur가 가리키는 노드의 바로 이전 노드를 가리키는 용도이므로 cur 포인터가 가리키는 노드의 바로 이전 노드를 가리키게 하면 된다. 이렇게 계속 반복했을 때 cur 포인터가 가리키는 노드가 마침내 ‘c’ 값을 가지는 경우, 우선 prev 포인터가 가리키는 노드의 포인터를 삭제하고자 하는 노드의 포인터가 가리키는 다음 노드를 가리키도록 한다. 즉, prev가 가리키는 노드의 포인터는 다음 다음 노드를 가리키게 된다. 그 후, 삭제하고자하는 노드 (cur 포인터가 가리키고 있는 노드)의 포인터를 null로 바꾸면 해당 노드를 삭제하게 되는 것이다.

특정 노드를 검색할 때도 이와 비슷하게 cur 포인터를 이용하면 된다. 검색 시에는 노드를 삽입하거나 삭제하는 것이 아니기에 기존 노드의 포인터 값을 변경시킬 필요는 없다.

연결 리스트 구현 예제

다음은 단일 연결 리스트를 구현하는 예제이다.

  • 단일 값만을 담는 노드에 대한 연결리스트

      class Node():
          def __init__(self, value=None, pointer=None):
              """
              하나의 노드를 구현하는 클래스.
              value: 노드의 값
              pointer: 현재 노드의 다음 노드를 가리키는 포인터.
              포인터에는 다음 노드의 메모리 주소값을 저장하므로
              해당 매개변수는 다음 Node 객체를 대입받아야 한다.
              """
              self.value = value
              self.pointer = pointer
        
      class LinkedList():
          def __init__(self):
              """
              self.head_pointer: 맨 앞 노드를 가리키는 포인터. Node 객체를 받는다.
              self.tail_pointer: 맨 뒤 노드를 가리키는 포인터. Node 객체를 받는다.
              self.length: 연결 리스트 내 총 노드의 개수.
              """
              self.head_pointer: Node | None = None
              self.tail_pointer: Node | None = None
              self.length = 0
        
          def getLength(self):
              """
              현재 연결 리스트 내 총 노드 수 반환.
              """
              return self.length
        
          def __repr__(self):
              """
              현재 연결 리스트를 출력.
              연결 리스트가 비어있으면 빈 문자열 출력.
              """
              linked_list = []
              current_node: Node | None = self.head_pointer
              while current_node is not None:
                  value_ = current_node.value
                  if type(value_) != str:
                      value_ = str(value_)
                  linked_list.append(value_)
                  current_node = current_node.pointer
              return '->'.join(linked_list) if linked_list else "빈 연결리스트."
        
          def addNodeFront(self, new_node: Node):
              """
              연결 리스트의 맨 앞에 새 노드를 삽입.
              """
              if self.head_pointer is None:
                  # 연결 리스트에 아무런 노드도 없는 경우.
                  self.head_pointer = new_node
                  self.tail_pointer = new_node
              else:
                  # 연결 리스트에 기존 노드들이 존재하는 경우.
                  new_node.pointer = self.head_pointer
                  self.head_pointer = new_node
              self.length += 1
        
          def addNodeBack(self, new_node: Node):
              """
              연결 리스트의 맨 뒤에 새 노드 삽입.
              """
              if self.tail_pointer is None:
                  # 연결 리스트에 아무 노드도 없는 경우.
                  self.tail_pointer = new_node
                  self.head_pointer = new_node
              else:
                  # 연결 리스트에 기존 노드들이 존재하는 경우.
                  self.tail_pointer.pointer = new_node
                  self.tail_pointer = new_node
              self.length += 1
        
          def findNodeByIndex(self, index: int) -> tuple[Node, int, Node | None]:
              """
              인덱스로 찾고자 하는 노드 반환.
              인덱스의 시작 번호는 0이다.
              현재 연결 리스트의 노드 개수보다 더 큰 인덱스 값 대입 시,
              또는 인덱스에 음수 입력 시
              IndexError 예외 발생.
              """
              if self.length < index + 1 or index < 0:
                  raise IndexError("연결 리스트의 길이에서 벗어나는 인덱스를 입력하였습니다.")
              cur_i = 0
              target_node = self.head_pointer
              prev_pointer = self.head_pointer
              while cur_i != index:
                  prev_pointer = target_node
                  target_node = target_node.pointer
                  cur_i += 1
              if index == 0:
                  prev_pointer = None
              return (target_node, index, prev_pointer)
            
          def findNodeByValue(self, target_value) -> tuple[Node, int, Node | None] | None:
              """
              찾고자 하는 값이 특정 노드의 값과 같은 경우 그 노드와 그 노드의 인덱스 반환.
              찾고자 하는 노드가 없다면 None을 반환.
              """
              current_node = self.head_pointer
                
              # 현재 선택된 노드의 이전 노드를 가리키는 포인터
              prev_pointer = self.head_pointer
        
              target_node = None
              target_index = 0
              while True:
                  if current_node is None:
                      break
                  if current_node.value == target_value:
                      target_node = current_node
                      if target_index == 0:
                          prev_pointer = None
                      break
                  prev_pointer = current_node
                  current_node = current_node.pointer
                  target_index += 1
              if target_node is None:
                  return None
              else:
                  return (target_node, target_index, prev_pointer)
                
          def insertNode(self, index: int, new_node: Node):
              """
              연결 리스트에서 지정된 인덱스 위치에 새 노드 삽입.
              """
              old_node, old_index, prev_pointer = self.findNodeByIndex(index)
              new_node.pointer = old_node
              if prev_pointer is None:
                  # 삽입하고자 하는 인덱스 위치가 0임.
                  self.head_pointer = new_node
              else:
                  prev_pointer.pointer = new_node
              self.length += 1
                
          def deleteNodeByIndex(self, index: int):
              """
              인덱스에 해당하는 노드 삭제.
              """
              target_node, target_index, prev_pointer = self.findNodeByIndex(index)
              if prev_pointer is None:
                  # 삭제하고자 하는 인덱스 위치가 0임.
                  self.head_pointer = target_node.pointer
              else:
                  prev_pointer.pointer = target_node.pointer
              target_node.pointer = None
              self.length -= 1
        
          def deleteNodeByValue(self, target_value):
              """
              삭제하고자 하는 값과 일치하는 노드를 삭제.
              """
              try:
                  search_result = self.findNodeByValue(target_value)
              except TypeError:
                  print("TypeError: 찾고자 하는 값이 없습니다.")
              else:
                  target_node, target_index, prev_pointer = search_result
                
              if target_index == 0:
                  self.head_pointer = target_node.pointer
              else:
                  prev_pointer.pointer = target_node.pointer
              target_node.pointer = None
              self.length -= 1
    

    다음은 위 파일을 테스트하는 파일과 그 결과이다.

      """
      my_linked_list의 LinkedList 테스트.
      LinkedList는 연결 리스트를 구현한 클래스이다.
      """
        
      import unittest
      from my_linked_list import Node, LinkedList
        
      class TestLinkedList(unittest.TestCase):
          def setUp(self) -> None:
              self.linked_list = LinkedList()
              self.desc = self.shortDescription()
        
              # 테스트에 따른 fixture 설정.
              if self.desc == "test_add_node_front":
                  self.linked_list.addNodeFront(Node('a'))
        
              elif self.desc == "test_add_node_back":
                  test_nodes = [Node('a'), Node('b')]
                  for one_node in test_nodes:
                      self.linked_list.addNodeBack(one_node)
        
              elif self.desc in (
                      'test_find_node_by_index',
                      'test_find_node_by_value',
                      'test_insert_node',
                      'test_delete_node_by_index',
                      'test_delete_node_by_value',
                  ):
                  test_values = ['a', 'b', 'c', 'd']
                  test_nodes = [Node(value) for value in test_values]
                  for one_node in test_nodes:
                      self.linked_list.addNodeBack(one_node)
        
          def test_empty(self):
              """
              test_empty\n
              비어있는 연결 리스트 출력 테스트
              """
              self.assertEqual(self.linked_list.__repr__(), "빈 연결리스트.")
              self.assertEqual(self.linked_list.getLength(), 0)
        
          def test_add_node_front(self):
              """
              test_add_node_front\n
              노드 하나를 맨 앞에 삽입하는 데에 성공하는지 테스트.
              """
              expected_result = '->'.join(['a'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 1)
        
          def test_add_node_back(self):
              """
              test_add_node_back\n
              노드 하나를 맨 뒤에 삽입하는 테스트.
              """
              expected_result = '->'.join(['a', 'b'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
          def test_find_node_by_index(self):
              """
              test_find_node_by_index\n
              주어진 인덱스의 노드를 찾을 수 있는지 테스트.
              """
              # test1
              test_index = 2
              actual_result = self.linked_list.findNodeByIndex(test_index)
              actual_result = (
                  actual_result[0].value, 
                  actual_result[1],
                  actual_result[2].value,
                  )
              expected_result = ('c', 2, 'b')
              self.assertEqual(actual_result, expected_result)
        
              # test2
              test_index = -1
              with self.assertRaises(IndexError):
                  self.linked_list.findNodeByIndex(test_index)
        
              # test3. IndexError 테스트.
              test_index = 4
              with self.assertRaises(IndexError):
                  self.linked_list.findNodeByIndex(test_index)
        
              # test4
              test_index = 0
              actual_result = self.linked_list.findNodeByIndex(test_index)
              actual_result = (
                  actual_result[0].value, 
                  actual_result[1],
                  actual_result[2],
                  )
              expected_result = ('a', 0, None)
              self.assertEqual(actual_result, expected_result)
        
          def test_find_node_by_value(self):
              """
              test_find_node_by_value\n
              주어진 값에 해당하는 노드를 찾을 수 있는지 테스트.
              """
              # test1
              test_value = 'b'
              actual_result = self.linked_list.findNodeByValue(test_value)
              actual_result = (
                  actual_result[0].value, 
                  actual_result[1],
                  actual_result[2].value
                  )
              expected_result = ('b', 1, 'a')
              self.assertEqual(actual_result, expected_result)
        
              # test2
              test_value = 'e'
              actual_result = self.linked_list.findNodeByValue(test_value)
              expected_result = None
              self.assertEqual(actual_result, expected_result)
        
              # test3
              test_value = 'a'
              actual_result = self.linked_list.findNodeByValue(test_value)
              actual_result = (
                  actual_result[0].value, 
                  actual_result[1],
                  actual_result[2],
                  )
              expected_result = ('a', 0, None)
              self.assertEqual(actual_result, expected_result)
                
          def test_insert_node(self):
              """
              test_insert_node\n
              특정 위치에 노드가 삽입되는지 테스트.
              """
              # test1
              target_index = 2
              new_node = Node('가')
              self.linked_list.insertNode(target_index, new_node)
              expected_result = '->'.join(['a', 'b', '가', 'c', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 5)
                
              # test2
              target_index = 0
              new_node = Node('나')
              self.linked_list.insertNode(target_index, new_node)
              expected_result = '->'.join(['나', 'a', 'b', '가', 'c', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 6)
                
          def test_delete_node_by_index(self):
              """
              test_delete_node_by_index\n
              지정된 인덱스 위치의 노드를 삭제할 수 있는지 테스트.
              """
              # test1
              target_index = 2
              self.linked_list.deleteNodeByIndex(target_index)
              expected_result = '->'.join(['a', 'b', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 3)
        
              # test2
              target_index = 0
              self.linked_list.deleteNodeByIndex(target_index)
              expected_result = '->'.join(['b', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              target_index = 5
              with self.assertRaises(IndexError):
                  self.linked_list.deleteNodeByIndex(target_index)
        
          def test_delete_node_by_value(self):
              """
              test_delete_node_by_value\n
              주어진 값과 일치하는 값을 가지는 노드를 삭제하는지 테스트.
              """
              # test1
              target_value = 'c'
              self.linked_list.deleteNodeByValue(target_value)
              expected_result = '->'.join(['a', 'b', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 3)
        
              # test2
              target_value = 'a'
              self.linked_list.deleteNodeByValue(target_value)
              expected_result = '->'.join(['b', 'd'])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              target_value = 'a'
              with self.assertRaises(TypeError):
                  self.linked_list.deleteNodeByValue(target_value)
        
      if __name__ == '__main__':
          unittest.main()
    
      ........
      ----------------------------------------------------------------------
      Ran 8 tests in 0.001s
        
      OK
    
  • 키-값 쌍 데이터를 담는 노드에 대한 연결리스트

      """
      키-값 형태의 데이터를 담을 수 있는 연결 리스트 구현.
      """
      from abc import *
        
      class _Node(metaclass=ABCMeta):
          @abstractmethod
          def __init__(self, key=None, value=None, pointer=None):
              pass
        
      class Node(_Node):
          def __init__(
                  self, 
                  key=None, 
                  value=None, 
                  pointer: _Node | None = None
              ) -> None:
              """
              하나의 노드를 구현하는 클래스.\n
              key: key-value의 key
              value: key-value의 value
              pointer: 현재 노드의 다음 노드를 가리키는 포인터.
              포인터에는 다음 노드의 메모리 주소값을 저장하므로
              해당 매개변수는 다음 Node 객체를 대입받아야 한다.
              """
              self.key = key
              self.value = value
              self.pointer = pointer
        
          def getKeyValueData(self):
              return (self.key, self.value)
        
      class LinkedList():
          def __init__(self) -> None:
              """
              self.head_pointer: 맨 앞 노드를 가리키는 포인터. Node 객체를 받는다.\n
              self.tail_pointer: 맨 뒤 노드를 가리키는 포인터. Node 객체를 받는다.\n
              self.length: 연결 리스트 내 총 노드의 개수.
              """
              self.head_pointer: Node | None = None
              self.tail_pointer: Node | None = None
              self.length = 0
        
          def clear(self):
              """
              연결 리스트를 비운다.
              즉, 모든 노드를 삭제한다.
              """
              self.head_pointer: Node | None = None
              self.tail_pointer: Node | None = None
              self.length = 0
        
          def getLength(self):
              """
              현재 연결 리스트 내 총 노드 수 반환.
              """
              return self.length
            
          def __repr__(self):
              """
              현재 연결 리스트를 출력.
              연결 리스트가 비어있으면 <Empty> 출력.
              """
              linked_list = []
              current_node: Node | None = self.head_pointer
              while current_node is not None:
                  key_ = current_node.key
                  value_ = current_node.value
                  if type(key_) != str:
                      key_ = str(key_)
                  if type(value_) != str:
                      value_ = str(value_)
                  linked_list.append(str((key_, value_)))
                  current_node = current_node.pointer
              return ' -> '.join(linked_list) if linked_list else "<Empty>"
            
          def addNodeFront(self, new_node: Node):
              """
              연결 리스트의 맨 앞에 새 노드를 삽입.\n
              만약 기존 노드의 키와 같은 키를 입력하는 경우, 
              기존 키의 값을 새 노드의 값으로 바꾼다. 
              """
              try:
                  old_node = self.findNodeByKey(new_node.key)[0]
              except TypeError:
                  # findNodeByKey() 의 반환값이 애초에 None인 경우.
                  pass
              else:
                  if old_node.key == new_node.key:
                      old_node.value = new_node.value
                      return
                
              if self.head_pointer is None:
                  # 연결 리스트에 아무런 노드도 없는 경우.
                  self.head_pointer = new_node
                  self.tail_pointer = new_node
              else:
                  # 연결 리스트에 기존 노드들이 존재하는 경우.
                  new_node.pointer = self.head_pointer
                  self.head_pointer = new_node
              self.length += 1
        
          def addNodeBack(self, new_node: Node):
              """
              연결 리스트의 맨 뒤에 새 노드 삽입.\n
              만약 기존 노드의 키와 같은 키를 입력하는 경우, 
              기존 키의 값을 새 노드의 값으로 바꾼다.
              """
              try:
                  old_node = self.findNodeByKey(new_node.key)[0]
              except TypeError:
                  # findNodeByKey() 의 반환값이 애초에 None인 경우.
                  pass
              else:
                  if old_node.key == new_node.key:
                      old_node.value = new_node.value
                      return
        
              if self.tail_pointer is None:
                  # 연결 리스트에 아무 노드도 없는 경우.
                  self.tail_pointer = new_node
                  self.head_pointer = new_node
              else:
                  # 연결 리스트에 기존 노드들이 존재하는 경우.
                  self.tail_pointer.pointer = new_node
                  self.tail_pointer = new_node
              self.length += 1
        
          def findNodeByIndex(self, index: int) -> tuple[Node, int, Node | None]:
              """
              인덱스로 찾고자 하는 노드 반환.
              인덱스의 시작 번호는 0이다.
              현재 연결 리스트의 노드 개수보다 더 큰 인덱스 값 대입 시,
              또는 인덱스에 음수 입력 시
              IndexError 예외 발생.
              """
              if self.length < index + 1 or index < 0:
                  raise IndexError("연결 리스트의 길이에서 벗어나는 인덱스를 입력하였습니다.")
              cur_i = 0
              target_node = self.head_pointer
              prev_pointer = self.head_pointer
              while cur_i != index:
                  prev_pointer = target_node
                  target_node = target_node.pointer
                  cur_i += 1
              if index == 0:
                  prev_pointer = None
              return (target_node, index, prev_pointer)
            
          def findNodeByKey(self, target_key) -> tuple[Node, int, Node | None] | None:
              """
              찾고자 하는 값이 특정 노드의 값과 같은 경우 그 노드와 그 노드의 인덱스 반환.
              찾고자 하는 노드가 없다면 None을 반환.
              """
              current_node = self.head_pointer
                
              # 현재 선택된 노드의 이전 노드를 가리키는 포인터
              prev_pointer = self.head_pointer
        
              target_node = None
              target_index = 0
              while True:
                  if current_node is None:
                      break
                  if current_node.key == target_key:
                      target_node = current_node
                      if target_index == 0:
                          prev_pointer = None
                      break
                  prev_pointer = current_node
                  current_node = current_node.pointer
                  target_index += 1
              if target_node is None:
                  return None
              else:
                  return (target_node, target_index, prev_pointer)
                
          def insertNode(self, index: int, new_node: Node):
              """
              연결 리스트에서 지정된 인덱스 위치에 새 노드 삽입.\n
              만약 기존 노드의 키와 같은 키를 입력하는 경우, 
              기존 키의 값을 새 노드의 값으로 바꾼다. 위치는 바꾸지 않는다.
              """
              try:
                  old_node = self.findNodeByKey(new_node.key)[0]
              except TypeError:
                  pass
              else:
                  if old_node.key == new_node.key:
                      old_node.value = new_node.value
                  return
        
              old_node, old_index, prev_pointer = self.findNodeByIndex(index)
              new_node.pointer = old_node
              if prev_pointer is None:
                  # 삽입하고자 하는 인덱스 위치가 0임.
                  self.head_pointer = new_node
              else:
                  prev_pointer.pointer = new_node
              self.length += 1
        
          def deleteNodeByIndex(self, index: int):
              """
              인덱스에 해당하는 노드 삭제.
              """
              target_node, target_index, prev_pointer = self.findNodeByIndex(index)
              if prev_pointer is None:
                  # 삭제하고자 하는 인덱스 위치가 0임.
                  self.head_pointer = target_node.pointer
              else:
                  prev_pointer.pointer = target_node.pointer
              target_node.pointer = None
              self.length -= 1
        
          def deleteNodeByKey(self, target_key):
              """
              삭제하고자 하는 값과 일치하는 노드를 삭제.
              """
              try:
                  search_result = self.findNodeByKey(target_key)
              except TypeError:
                  print("TypeError: 찾고자 하는 값이 없습니다.")
              else:
                  target_node, target_index, prev_pointer = search_result
                
              if target_index == 0:
                  self.head_pointer = target_node.pointer
              else:
                  prev_pointer.pointer = target_node.pointer
              target_node.pointer = None
              self.length -= 1
        
          def getValue(self, key):
              """
              key가 주어지면 그에 해당하는 value를 반환.
              해당하는 value가 없으면 None 반환.
              """
              try:
                  target_data = self.findNodeByKey(key)[0].value
              except TypeError:
                  return None
              return target_data
            
          def getAllData(self) -> list[tuple]:
              """
              연결 리스트 내 모든 데이터를 반환.\n
              반환값 형태: [(key1, value1), (key2, value2), ...]
              """
              current_node = self.head_pointer
              all_data = []
              while current_node is not None:
                  all_data.append((current_node.key, current_node.value))
                  current_node = current_node.pointer
              return all_data
        
      if __name__ == '__main__':
          linked_list = LinkedList()
          print(linked_list)
        
          linked_list.addNodeFront(Node('사과', 'apple'))
          linked_list.addNodeFront(Node('바나나', 'banana'))
          print(linked_list)
          print(linked_list.getLength())
    

    다음은 위 파일을 테스트하는 파일이다.

      import unittest
      from linked_list_kv import Node, LinkedList
        
      def insertAllNodes(
              ll: LinkedList, 
              kv_data: list[tuple],
              insert_mode: str
          ):
          """
          여러 데이터들을 연결 리스트에 한 번에 넣는다.\n
          insert_mode)
          'f': addNodeFront() 메서드 사용.
          'b': addNodeBack() 메서드 사용.
          """
          for one_data in kv_data:
              if insert_mode == 'f':
                  ll.addNodeFront(Node(one_data[0], one_data[1]))
              else:
                  ll.addNodeBack(Node(one_data[0], one_data[1]))
        
      class TestMyLinkedList(unittest.TestCase):
          def setUp(self) -> None:
              self.linked_list = LinkedList()
              self.desc = self.shortDescription()
        
              # 테스트에 따른 fixture 설정.
              if self.desc in (
                  'test_find_node_by_index', 'test_find_node_by_key',
                  'test_get_value', 'test_insert_node',
                  'test_delete_node_by_index', 'test_delete_node_by_key',
                  'test_get_all_data',
                  ):
                  self.linked_list.clear()
                  dataset = [
                      ('사과', 'apple'),
                      ('바나나', 'banana'),
                      ('자전거', 'cycle'),
                      ('지연', 'delay'),
                  ]
                  insertAllNodes(self.linked_list, dataset, 'b')
        
          def test_empty(self):
              """
              test_empty\n
              비어있는 연결 리스트 출력 테스트
              """
              self.assertEqual(self.linked_list.__repr__(), "<Empty>")
              self.assertEqual(self.linked_list.getLength(), 0)
        
          def test_clear(self):
              """
              test_clear\n
              연결 리스트를 비울 수 있는지 테스트.
              """
              dataset = [
                  ('사과', 'apple'),
                  ('바나나', 'banana'),
                  ('자전거', 'cycle'),
              ]
              insertAllNodes(self.linked_list, dataset, 'f')
              expected_result = "('자전거', 'cycle') -> ('바나나', 'banana') -> ('사과', 'apple')"
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), len(dataset))
        
              self.linked_list.clear()
              self.assertEqual(self.linked_list.__repr__(), "<Empty>")
              self.assertEqual(self.linked_list.getLength(), 0)
        
          def test_add_node_front(self):
              """
              test_add_node_front\n
              노드 하나를 맨 앞에 삽입하는 데에 성공하는지 테스트.
              """
              # test1
              data1 = ('사과', 'apple')
              data_list = [data1]
              self.linked_list.addNodeFront(Node(data1[0], data1[1]))
              expected_result = ' -> '.join([str(('사과', 'apple'))])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 1)
        
              # test2
              data2 = ('바나나', 'banana')
              data_list.insert(0, data2)
              self.linked_list.addNodeFront(Node(data2[0], data2[1]))
              expected_result = ' -> '.join([str(data) for data in data_list])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              # 기존 노드의 키와 같은 키 입력 시 새로운 노드의 value로 바꾸는지 
              # 테스트.
              data3 = ('바나나', 'BANANA')
              data_list.remove(data2)
              data_list.insert(0, data3)
              self.linked_list.addNodeFront(Node(data3[0], data3[1]))
              expected_result = ' -> '.join([str(data) for data in data_list])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
          def test_add_node_back(self):
              """
              test_add_node_back\n
              노드 하나를 맨 뒤에 삽입하는 테스트.
              """
              # test1
              data1 = ('사과', 'apple')
              data_list = [data1]
              self.linked_list.addNodeBack(Node(data1[0], data1[1]))
              expected_result = ' -> '.join([str(('사과', 'apple'))])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 1)
        
              # test2
              data2 = ('바나나', 'banana')
              data_list.append(data2)
              self.linked_list.addNodeBack(Node(data2[0], data2[1]))
              expected_result = ' -> '.join([str(data) for data in data_list])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              # 기존 노드의 키와 같은 키 입력 시 새로운 노드의 value로 바꾸는지 
              # 테스트.
              data3 = ('바나나', 'BANANA')
              data_list.remove(data2)
              data_list.append(data3)
              self.linked_list.addNodeBack(Node(data3[0], data3[1]))
              expected_result = ' -> '.join([str(data) for data in data_list])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
          def test_find_node_by_index(self):
              """
              test_find_node_by_index\n
              주어진 인덱스의 노드를 찾을 수 있는지 테스트.
              """
              # test1
              index = 2
              actual_result = self.linked_list.findNodeByIndex(index)
              actual_result = (
                  (actual_result[0].key, actual_result[0].value),
                  actual_result[1],
                  (actual_result[2].key, actual_result[2].value),
              )
              expected_result = (('자전거', 'cycle'), 2, ('바나나', 'banana'))
              self.assertEqual(actual_result, expected_result)
        
              # test2
              index = 0
              actual_result = self.linked_list.findNodeByIndex(index)
              actual_result = (
                  (actual_result[0].key, actual_result[0].value),
                  actual_result[1],
                  actual_result[2],
              )
              expected_result = (('사과', 'apple'), 0, None)
              self.assertEqual(actual_result, expected_result)
        
              # test3
              index = -1
              with self.assertRaises(IndexError):
                  self.linked_list.findNodeByIndex(index)
        
              # test4
              index = 4
              with self.assertRaises(IndexError):
                  self.linked_list.findNodeByIndex(index)
        
          def test_find_node_by_key(self):
              """
              test_find_node_by_key\n
              주어진 키에 해당하는 노드를 찾을 수 있는지 테스트.
              """
              # test1
              test_key = '바나나'
              actual_result = self.linked_list.findNodeByKey(test_key)
              actual_result = (
                  (actual_result[0].key, actual_result[0].value),
                  actual_result[1],
                  (actual_result[2].key, actual_result[2].value),
              )
              expected_result = (('바나나', 'banana'), 1, ('사과', 'apple'),)
              self.assertEqual(actual_result, expected_result)
        
              # test2
              test_key = '사과'
              actual_result = self.linked_list.findNodeByKey(test_key)
              actual_result = (
                  (actual_result[0].key, actual_result[0].value),
                  actual_result[1],
                  actual_result[2],
              )
              expected_result = (('사과', 'apple'), 0, None,)
              self.assertEqual(actual_result, expected_result)
        
              # test3
              test_key = '전자'
              actual_result = self.linked_list.findNodeByKey(test_key)
              expected_result = None
              self.assertEqual(actual_result, expected_result)
        
          def test_insert_node(self):
              """
              test_insert_node\n
              특정 위치에 노드가 삽입되는지 테스트.
              """
              # test1
              test_index = 2
              new_node = Node('전자', 'electron')
              self.linked_list.insertNode(test_index, new_node)
              expected_result = ' -> '.join([
                  "('사과', 'apple')",
                  "('바나나', 'banana')",
                  "('전자', 'electron')",
                  "('자전거', 'cycle')",
                  "('지연', 'delay')"
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 5)
        
              # test2
              test_index = 0
              new_node = Node('라면', 'ramen')
              self.linked_list.insertNode(test_index, new_node)
              expected_result = ' -> '.join([
                  "('라면', 'ramen')",
                  "('사과', 'apple')",
                  "('바나나', 'banana')",
                  "('전자', 'electron')",
                  "('자전거', 'cycle')",
                  "('지연', 'delay')"
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 6)
        
              # test3
              test_index = 10
              new_node = Node()
              with self.assertRaises(IndexError):
                  self.linked_list.insertNode(test_index, new_node)
        
              # test4
              # 기존 노드의 키가 새로 삽입할 노드의 키가 같은 경우,
              # 기존 노드의 value를 새로 삽입할 노드의 value로 바꾸는 지 테스트.
              test_index = 4
              new_node = Node('라면', 'RAMEN')
              self.linked_list.insertNode(test_index, new_node)
              expected_result = ' -> '.join([
                  "('라면', 'RAMEN')",
                  "('사과', 'apple')",
                  "('바나나', 'banana')",
                  "('전자', 'electron')",
                  "('자전거', 'cycle')",
                  "('지연', 'delay')"
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 6)
                
        
          def test_delete_node_by_index(self):
              """
              test_delete_node_by_index\n
              지정된 인덱스 위치의 노드를 삭제할 수 있는지 테스트.
              """
              # test1
              target_index = 2
              self.linked_list.deleteNodeByIndex(target_index)
              expected_result = ' -> '.join([
                  "('사과', 'apple')",
                  "('바나나', 'banana')",
                  "('지연', 'delay')",
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 3)
        
              # test2
              target_index = 0
              self.linked_list.deleteNodeByIndex(target_index)
              expected_result = ' -> '.join([
                  "('바나나', 'banana')",
                  "('지연', 'delay')",
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              target_index = 5
              with self.assertRaises(IndexError):
                  self.linked_list.deleteNodeByIndex(target_index)
        
          def test_delete_node_by_key(self):
              """
              test_delete_node_by_key\n
              주어진 key와 일치하는 데이터를 가지는 노드를 삭제하는지 테스트.
              """
              # test1
              target_key = '바나나'
              self.linked_list.deleteNodeByKey(target_key)
              expected_result = ' -> '.join([
                  "('사과', 'apple')",
                  "('자전거', 'cycle')",
                  "('지연', 'delay')",
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 3)
        
              # test2
              target_key = '사과'
              self.linked_list.deleteNodeByKey(target_key)
              expected_result = ' -> '.join([
                  "('자전거', 'cycle')",
                  "('지연', 'delay')",
              ])
              self.assertEqual(self.linked_list.__repr__(), expected_result)
              self.assertEqual(self.linked_list.getLength(), 2)
        
              # test3
              target_key = '사과'
              with self.assertRaises(TypeError):
                  self.linked_list.deleteNodeByKey(target_key)
        
          def test_get_value(self):
              """
              test_get_value\n
              key가 주어지면 그에 해당하는 value를 반환.
              """
              # test1
              test_key = '사과'
              actual_result = self.linked_list.getValue(test_key)
              expected_result = 'apple'
              self.assertEqual(actual_result, expected_result)
        
              # test2
              test_key = '전자'
              actual_result = self.linked_list.getValue(test_key)
              expected_result = None
              self.assertEqual(actual_result, expected_result)
        
          def test_get_all_data(self):
              """
              test_get_all_data\n
              연결 리스트 내 모든 데이터 출력 테스트.
              """
              actual_result = self.linked_list.getAllData()
              expected_result = [
                  ('사과', 'apple'),
                  ('바나나', 'banana'),
                  ('자전거', 'cycle'),
                  ('지연', 'delay'),
              ]
              self.assertEqual(actual_result, expected_result)
        
      if __name__ == '__main__':
          unittest.main()
    
      ...........
      ----------------------------------------------------------------------
      Ran 11 tests in 0.002s
        
      OK
    

보충 설명

연결 리스트의 크기는 동적일 수 있어서, 실행 중에 저장할 데이터들의 수를 알 수 없을 때 사용할 수 있다.


Reference

[1] 지은이: 미아 스타인, 옮긴이: 최길우, “파이썬 자료구조와 알고리즘”, (2019, 한빛미디어)

[2] [자료구조] 연결리스트(Linked List)의 개념, 이해 | 단순연결리스트(Singly linked list) C언어 구현, 소스코드

[3] 연결 리스트

This content is licensed under CC BY-NC 4.0

댓글남기기