본문 바로가기

6.5. Data Sturcture

6/15(목) IT 자료구조(4일차) / 5.원형 연결리스트

 

 

 

5. 원형 연결리스트(Circular LinkedList)

원형 연결 리스트는 연결 리스트의 한 종류로 마지막 노드가 첫 번째 노드를 가리키며,

첫 번째 노드의 이전 노드는 마지막 노드를 가리킴.

이러한 구조로 인해 리스트의 끝과 시작이 연결되어 있는 원형 모양을 이루게 됨.

원형 연결 리스트의 장점:

1) 순환 구조: 리스트의 마지막 노드가 첫 번째 노드를 가리키므로, 순환적으로 데이터에 접근할 수 있음.

이를 통해 순차적인 반복 작업을 수행할 때 유용함.
2) 리스트 순회: 원형 연결 리스트는 리스트의 끝과 시작이 연결되어 있으므로,

리스트를 순회하는 데 있어서 종료 조건을 명시할 필요가 없음.

순회 작업을 할 때 끝에 도달하면 다시 첫 번째 노드로 돌아갈 수 있음.
3) 데이터 삽입/삭제: 새로운 노드를 삽입하거나 기존의 노드를 삭제할 때,

주어진 위치에 상관없이 상수 시간에 가능하여 삽입이나 삭제 작업에 대한 효율성이 높아짐.

그러나 원형 연결 리스트는 일반적인 연결 리스트보다 구현이 조금 복잡할 수 있음.

삽입, 삭제, 검색 작업 시 노드의 연결 상태를 잘 관리해야 하며,

순회 작업 시 무한 루프에 빠지지 않도록 주의해야 함.

또한, 원형 연결 리스트의 크기를 동적으로 변경하기 어려울 수 있음.

원형 연결 리스트는 주로 특정 상황에서 활용되며, 

예를 들어 라운드 로빈 스케줄링, 알고리즘 문제 해결 등에 유용하게 사용될 수 있음.

 

 

5-1. 이터레이터(Iterator)

이터레이터(Iterator)는 컬렉션 내부의 요소들에 순차적으로 접근할 수 있는 객체.

이터레이터는 컬렉션의 내부 구조에 대한 세부 정보를 노출시키지 않으면서

요소들을 반복적으로 처리할 수 있는 방법을 제공함.


주로 다음과 같은 두 가지 메서드를 제공:

next(): 이 메서드는 다음 요소를 반환함.

만약 컬렉션의 끝에 도달한 경우, 즉 모든 요소를 순회한 경우에는 예외(보통 StopIteration)를 발생시킴.

iter(): 이 메서드는 이터레이터 객체 자신을 반환함.

이를 통해 이터레이터를 직접 반복 가능한 객체(Iterable)로 만들 수 있음.

이터레이터가 반복 가능한 객체로 변환되면 for 루프와 같은 반복 구문에서 사용할 수 있음.

이터레이터는 파이썬과 같은 많은 프로그래밍 언어에서 사용되며, 

컬렉션의 크기나 내부 구조에 상관없이 일관된 방식으로 요소에 접근할 수 있게 해줌.

이를 통해 메모리를 효율적으로 사용하고, 대용량 데이터셋에 대한 반복 작업을 가능하게 함.

파이썬에서는 많은 내장 컬렉션(예: 리스트, 튜플, 집합, 딕셔너리)과 함께 이터레이터를 지원하며, 

iter() 함수와 next() 함수를 사용하여 이터레이터 객체를 생성하고 다음 요소에 접근할 수 있음.

또한, 컴프리헨션, 제너레이터 등의 기능을 활용하여 사용자 정의 이터레이터를 생성할 수도 있음.

 

종류:

리스트 이터레이터(List Iterator): 리스트(List)의 요소를 반복적으로 처리하기 위한 이터레이터.

list 객체의 iter() 메서드를 호출하여 생성할 수 있음.
튜플 이터레이터(Tuple Iterator): 튜플(Tuple)의 요소를 반복적으로 처리하기 위한 이터레이터.

tuple 객체의 iter() 메서드를 호출하여 생성할 수 있음.
집합 이터레이터(Set Iterator): 집합(Set)의 요소를 반복적으로 처리하기 위한 이터레이터.

set 객체의 iter() 메서드를 호출하여 생성할 수 있음.
딕셔너리 이터레이터(Dictionary Iterator): 딕셔너리(Dictionary)의 키(key) 또는 값(value)을 반복적으로 처리하기 위한 이터레이터. dict 객체의 keys(), values(), items() 메서드를 호출하여 생성할 수 있음.

문자열 이터레이터(String Iterator): 문자열(String)의 각 문자를 반복적으로 처리하기 위한 이터레이터.

str 객체의 iter() 메서드를 호출하여 생성할 수 있음.
사용자정의 이터레이터(Custom Iterator): 커스텀 데이터 구조나 객체에서 요소를 반복적으로 처리하기 위해 사용자가 직접 정의한 이터레이터.

사용자 정의 클래스 내에서 __iter__()와 __next__() 메서드를 구현하여 생성할 수 있음.

iterator를 통해 개선된 형태:
.extend(): 이전에는 += 연산자를 사용하여 다른 리스트를 현재 리스트에 추가하는 방식으로 구현하였으나,

이터레이터를 활용하면 .extend() 메서드를 사용하여 좀 더 간결하고 효율적인 코드를 작성할 수 있음.

.extend() 메서드는 다른 이터러블(iterable) 객체의 모든 요소를 현재 리스트에 추가함.

# 이전 방식
my_list = [1, 2, 3]
other_list = [4, 5, 6]
my_list += other_list
# 개선된 방식
my_list = [1, 2, 3]
other_list = [4, 5, 6]
my_list.extend(other_list)

.copy() 메서드: 이전에는 .copy() 메서드를 사용하여 리스트를 복사하는 방식으로 구현할 수 있었으나,

.copy() 메서드를 호출하는 대신 슬라이싱(Slicing)을 통해 좀 더 간결하고 빠른 코드를 작성할 수 있음.

슬라이싱을 사용하여 리스트 전체를 복사할 수 있음.
# 이전 방식
my_list = [1, 2, 3]
new_list = my_list.copy()
# 개선된 방식
my_list = [1, 2, 3]
new_list = my_list[:]

.reverse() 메서드: 이전에는 .reverse() 메서드를 사용하여 리스트의 요소 순서를 뒤집는 방식으로 구현하였으나, 이터레이터를 활용하면 리스트를 슬라이싱하여 좀 더 간결하게 코드를 작성할 수 있음.

리스트를 [::-1]과 같이 슬라이싱하면 요소의 순서가 뒤집힌 새로운 리스트를 반환함.
# 이전 방식
my_list = [1, 2, 3]
my_list.reverse()
# 개선된 방식
my_list = [1, 2, 3]
my_list = my_list[::-1]

 

#  다양한 데이터 구조를 활용하여 이터레이터(Iterator)를 생성하고 사용하는 예시.
lit1 = [1,2,3,4,5]

# lit1이라는 리스트를 생성하고 [1, 2, 3, 4, 5]로 초기화
print(lit1, type(lit1))

# lit1 리스트와 해당 객체의 타입을 출력.

# 결과는 [1, 2, 3, 4, 5] <class 'list'>와 같이 리스트와 리스트 객체의 타입을 나타냄.
print(dir(lit1))
# lit1 리스트 객체가 가지고 있는 속성과 메서드들을 출력함.

결과는 해당 리스트 객체의 모든 속성과 메서드를 나열한 리스트를 나타냄.


litr = iter(lit1) # lit1 리스트를 이터레이터로 변환하기 위해 iter() 함수를 사용하여 litr 변수에 할당함.
print(litr, type(litr)) # litr 이터레이터와 해당 객체의 타입을 출력함.
print(litr.__next__()) # 1이 출력.
print(litr.__next__()) # 2가 출력.
print(litr.__next__()) # 3이 출력.
print(litr.__next__()) # 4가 출력.
print(litr.__next__()) # 5가 출력.
print(litr.__next__()) # 더 이상 반복할 요소가 없기 때문에 __next__() 메서드 호출시 StopIteration 예외 발생.

liter = lit1.__iter__()  # lit1 리스트의 __iter__() 메서드를 호출하여 이터레이터 객체 liter를 생성.
print(liter, type(liter)) # 이터레이터와 해당 객체의 타입을 출력
print(liter.__next__()) # 1이 출력.
print(liter.__next__()) # 2가 출력.

dct = {'a': 'apple', 'b': 'banana'} # dct라는 딕셔너리를 생성하고 {'a': 'apple', 'b': 'banana'}로 초기화.
print(dir(dct))

st = {1,2,3} # st라는 세트(Set)를 생성하고 {1, 2, 3}으로 초기화.
print(dir(st))

5-2. 구현

class ListNode:
  def __init__(self, new_item, next_node):
    self.item = new_item
    self.next = next_node

class CircularLinkedList:
  def __init__(self):
    self.tail = ListNode('dummy', None)
    self.tail.next = self.tail
    self.num_items = 0

  def insert(self, i:int, new_item):
    if i >= 0 and i <= self.num_items:
      prev = self.get_node(i - 1)
      new_node = ListNode(new_item, prev.next)
      prev.next = new_node
      if i == self.num_items:
        self.tail = new_node
      self.num_items += 1
    else:
      print(f'{i}는 범위 안의 인덱스가 아닙니다.')

  # 특정 노드의 위치 찾기 : 인덱스가 없어서 직접 찾아야함.
  def get_node(self, i:int):
    curr = self.tail.next # 더미헤드
    for index in range(i+1):
      curr = curr.next
    return curr

  def append(self, new_item):
    new_node = ListNode(new_item, self.tail.next)
    self.tail.next = new_node
    self.tail = new_node
    self.num_items += 1

  def pop(self, *args):
    if self.is_empty():
      return None
    if len(args) != 0:
      i = args[0]
    # pop 기본값 -1 설정
    if len(args) == 0 or i == -1:
      i = self.num_items - 1
    if i >= 0 and i < self.num_items:
      prev = self.get_node(i-1)
      ret = prev.next.item # curr.item
      prev.next = prev.next.next
      self.num_items -= 1
      return ret
    else:
      return None

  def remove(self, x):
    (prev, curr) = self.find_node(x)
    if curr != None:
      prev.next = curr.next
      if curr == self.tail:
        self.tail = prev
      self.num_items -= 1
      return x
    else:
      return None

  def find_node(self, x):
    # head : 반복문이 종료될 조건 -> curr가 더미헤드를 가리킬 때
    # head = prev = self.tail.next
    head = self.tail.next
    prev = self.tail.next
    curr = prev.next # 0번 노드
    while curr != head:
      if curr.item == x:
        return (prev, curr)
      else:
        prev = curr
        curr = curr.next
    return (None, None)

  def get(self, i: int):
    if self.is_empty():
      return None
    if i >= 0 and i < self.num_items:
      return self.get_node(i).new_item
    elif i < 0:
      return self.get_node(self.num_items + i).item
    else:
      return None