자료구조(Data Structure), 추상자료형(ADT), Queue, Stack
자료구조를 생각할 때 오늘도 역시 'python'에 집중하기보단 컴퓨터 공학에서 말하는 자료구조의 맥락에서 생각해야 한다!
자료 구조의 목적
자료 구조의 활용
추상 자료형 (ADT, Abstract Data Type) - 자료구조의 핵심!
추상화
에 대해서 다룬적이 있는데 오늘도 자료 구조와 관련해 이를 다시 다뤘다.핵심요약
이다. 오늘 좋은 예시를 보았는데, 네 각이 모두 직각이다. 네 변의 길이가 같다.
를 우리는 그냥 정사각형
이라고 부른다. 이것도 추상화라고 함! (네이버 지식백과에 나온 예시 참고.)연결 리스트(linked-list)
, Queue
, stack
도 마찬가지다. 이런 ADT는 기본 자료형인 리스트, 숫자, 문자열 등을 활용하여 사용자에 의해 구현된다.대표적인 ADT인 queue와 stack에 대해서 배웠는데 좋은 예시가 많아서 이해하는데 크게 어렵지는 않았다.
Queue
First IN First OUT
이라고 할 수 있다.enqueue
는 큐에 값을 집어넣는 함수고, dequeue
는 빼내는 함수다.) 파이썬으로 구현할 때 rear, front를 지정해주는 이유다. deque
라고 collections 모듈에 있는게 있어서 이걸로 큐 구현을 쉽게 할 수도 있다. (그 외 방법은 실습 코드의 레퍼런스 주석 참고!)# 리스트 메소드를 사용해서 큐를 만들어보기
from collections import deque
queue = deque(["Eric", "John", "Michael"])
queue.append("Terry")
queue.append("Graham")
print('queue:', queue)
print('queue.popleft():',queue.popleft())
print('queue.popleft():',queue.popleft())
print('queue:', queue)
Stack
Last In First OUT
이다.push
라고 하고, 최상위 데이터를 꺼내는 기능을 pop
이라고 한다.top
,peek
,head
가 있다! 연결 리스트가 뭔지 정리하기 전에, 먼저 배열
에 대해서 적어보자!
배열이란?
선형 자료구조
라고 하는데, 이 말은 데이터들을 일렬로 나열된 형태로 저장되어 있다는 뜻이다. 배열에는 정적 배열
과 동적 배열
이 있다.
(정적) 배열
동적 배열
자 배열에 대해서 알아봤으니 이제 배열의 단점을 보완한 연결 리스트에 대해서 알아보자!
다시 한 번 강조하고 싶은 건, 배열의 단점을 보완하기 위해 나온게 연결리스트라고 해서 연결리스트가 배열보다 무조건 좋다고는 할 수 없다는 것이다. 자료 구조 생각할 때는 언제나 내 목적에 맞는 가장 좋은 자료구조를 고르는 것이라는 것 유의하자!
연결 리스트가 뭔지는 우선 아래의 그림을 보자.
단방향 연결 리스트
라고 하고, 이전 노드, 다음 노드의 정보 모두를 갖고 있다면 양방향 연결 리스트
라고 부른다. (원형(환영) 연결 리스트
라는 것도 있긴 함) O(1)
이다.
- queue, stack을 파이썬 리스트를 통해 구현하기
- 파이썬으로 연결리스트 구현하기
코드 잘 보면 각 자료 구조에 대해 이해하기 도움되니 나중에도 잘 참고하자.
[리스트 메서드 이용해서 큐, 스택 구현하기]
"""
Bare Minimum Requirements
요구사항
큐, 스택을 구현해주세요.
각 메소드에 작성되어있는 문제를 확인하여 코드를 작성해주세요.
리스트 메소드를 활용해서 구현해보세요.
메소드 이름은 변경하지 마세요.
메소드의 매개변수를 추가하거나 삭제하지 마세요.
"""
# 문제에서 리스트 메소드를 활용해서 구현하라고 한 것 놓쳐선 안됨!
class Queue(): # reference https://daimhada.tistory.com/107 (리스트 뿐 아니라 다양한 방법으로 큐를 구현하는 예시를 볼 수 있음.)
def __init__(self):
"""
# 문제 1.
Queue의 생성자 함수를 구현해주세요.
Queue 생성시 데이터를 담을 공간을 만들어주세요.
구현하시려는 로직에 맞게 해당 함수를 구현해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.queue = [] # 빈 리스트(큐)를 생성한다.
def enqueue(self, item):
"""
#. 문제 2.
queue에 item 매개변수에 들어온 값을 집어넣는 메소드를 구현해주세요.
input: item
queue로 들어갈 값입니다.
output:
반환값은 없습니다.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.queue.append(item) # 리스트의 끝에 더해나가도록 한다.
def dequeue(self):
"""
#. 문제 3.
queue 동작에 알맞게 값을 queue에서 삭제하는 메소드를 구현해주세요.
input:
input은 없습니다.
output:
queue동작에 맞게 queue에서 삭제된 값을 반환해주세요.
만약 삭제한 값이 없다면 None을 반환해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
# 가장 처음 들어간 값(=리스트의 맨 앞 요소)를 추출하자. (input이 없다고 했으므로)
if len(self.queue) == 0: # 만약 큐가 비어있어 삭제할 게 없다면 None을 반환하라.
return None
else:
return self.queue.pop(0) # 큐가 비어있지 않다면 가장 첫 값을 추출하고 반환해라.
def return_queue(self):
"""
#. 문제 4.
queue내부에 있는 값을 반환하는 메소드를 구현해주세요.
input:
input은 없습니다.
output:
queue내부에 있는 값을 리스트 형태로 반환해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
return self.queue
'''
[기록] 만든거 테스트해보기 - 잘 되는군.
qtest = Queue() # 객체 생성
print('first enqueue= ', qtest.enqueue(5)) # => first enqueue= None
print('return enqueue= ', qtest.return_queue()) # => [5]
print('delete queue= ', qtest.dequeue()) # => 5
print('return enqueue= ', qtest.return_queue()) # => []
print('delete empty enqueue= ', qtest.dequeue()) # => None
'''
class Stack():
def __init__(self):
"""
# 문제 5.
Stack의 생성자 함수를 구현해주세요.
Stack 생성시 데이터를 담을 공간을 만들어주세요.
구현하시려는 로직에 맞게 해당 함수를 구현해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.stack = []
def push(self, item):
"""
#. 문제 6.
Stack에 item 매개변수에 들어온 값을 집어넣는 메소드를 구현해주세요.
input: item
Stack로 들어갈 값입니다.
output:
반환값은 없습니다.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.stack.append(item)
def pop(self):
"""
#. 문제 7.
Stack 동작에 알맞게 값을 Stack에서 삭제하는 메소드를 구현해주세요.
input:
input은 없습니다.
output:
Stack동작에 맞게 Stack에서 삭제된 값을 반환해주세요.
만약 삭제한 값이 없다면 None을 반환해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
if len(self.stack) == 0:
return None
else:
return self.stack.pop() # 가장 마지막에 나온 값을 출력하고 제거한다.
def return_stack(self):
"""
#. 문제 8.
Stack내부에 있는 값을 반환하는 메소드를 구현해주세요.
input:
input은 없습니다.
output:
Stack내부에 있는 값을 리스트 형태로 반환해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
return self.stack
[연결리스트 구현하기]
"""
Bare Minimum Requirements
연결리스트의 개념은 변수의 인덱스에 직접접근하는 배열개념과는 다릅니다.
참조의 개념을 활용하여 변수에 접근하는 방법을 익혀봅니다.
요구사항:
주어진 문제는 연결리스트지만 독립적으로 개념이 운용되는 것은 아닙니다.
연결리스트를 구현해주세요.
각 메소드에 작성되어있는 문제를 확인하여 코드를 작성해주세요.
단 collections 라이브러리는 사용하지 마세요.
메소드 이름은 변경하지 마세요.
메소드의 매개변수를 추가하거나 삭제하지 마세요.
"""
class Node:
def __init__(self,value,next=None):
"""
# 문제 1.
Linkedlist에서 사용할 Node의 생성자 함수를 구현해주세요.
속성값: value, next
value: Node의 값
next: 생성될 Node의 다음 Node, 기본값은 None
output:
반환값은 없습니다.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.value = value
self.next = next
class linked_list:
def __init__(self, value):
"""
# 문제 2.
Linkedlist의 생성자 함수를 구현해주세요.
속성값: head
head: Linkedlist의 value값을 가진 Head Node
output:
반환값은 없습니다.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
self.head = Node(value) # Node 클래스를 통해 생성한 노드를 head로 지정. next = None.
def add_node(self, value):
"""
# 문제 3.
Linkedlist에 새로운 Node를 추가하는 메소드를 작성해주세요.
input: value
value: Linkedlist에 들어올 새로운 Node Value
output:
반환값은 없습니다.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
if self.head == None: # 연결 리스트에 노드가 del_node로 없는 경우를 대비.
self.head = Node(value) # 그럴땐 새로 넣는 value를 가진 노드를 만들어 head로 지정.
else: # 연결 리스트에 값이 존재하는 경우
node = self.head
while node.next: # head의 next에, 그 다음 next에 ..(반복).. node.next = None일 때까지.
node = node.next # 즉, 마지막 노드까지 가는 것.
node.next = Node(value) # 위에서 찾은 마지막 node의 next에 새로운 node를 추가하라.
def del_node(self,value):
"""
# 문제 4.
Linkedlist에 value값을 가지고 있는 Node를 삭제하는 메소드를 작성해주세요.
input: value
value: Linkedlist에서 삭제할 Node Value
output:
값을 삭제하였다면 삭제한 Node의 value를 반환
만약 LinkedList에 값이 없다면 None 반환
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
if self.head == None: # 연결 리스트에 head가 아예 없는 경우엔 삭제고 뭐고 할 필요가 없음.
return None
elif self.head.value == value: # 만약 삭제하려는 값이 head의 값이었다면?
tmp = self.head # 원래 head였던 노드를 변수에 할당.
self.head = self.head.next # head 노드를 원래 헤드의 다음 노드로 새로 지정하고
del tmp # 원래 헤드였지만 지금은 헤드가 아닌 그 노드를 삭제하라!
return value # 이거 나중에 헷갈릴 수 있겠다. 연결 리스트로 된 노드들을 직접 그림으로 그려보면서 생각해보자.
else:
node = self.head # 항상 head 노드부터 시작.
while node.next:
if node.next.value == value: # 내가 지우려는 값을 찾았다면?
tmp = node.next
node.next = node.next.next # 내가 지우려는 노드의 다음 값을 원래 노드의 next로 바꾸고
del tmp # 지우려는 노드 지워!
return value
else:
node = node.next # 못 찾았으면 다음 노드로 가!
# 값이 여러 노드에 있으면 어떻게 되지?
def ord_desc(self):
"""
# 문제 5.
Linkedlist에 저장된 값들을 리스트 형태로 반환하는 메소드를 작성해주세요.
input:
input값은 없습니다.
output:
Linkedlist의 Head부터 시작하여 저장된 모든 Node의 Value들을
리스트 형태로 반환해주세요.
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
res = [] # value 모아서 리턴할 리스트
node = self.head
while node: # 노드가 있을 때까지 반복
res.append(node.value)
node = node.next # 다음 노드로 'node'를 변경.
return res
def search_node(self,value):
"""
# 문제 6.
Linkedlist에 value값이 어디에 위치하는지 찾는 메소드를 작성해주세요.
input: value
value: Linkedlist내부에서 찾고자 하는 값
output:
연결리스트에서 value를 가진 노드를 찾아 노드를 반환
아래 pass를 지워주시고 코드를 작성해주시면 됩니다.
"""
node = self.head # 연결리스트는 늘 head 부터 탐색한다는 점 기억!
while node:
if node.value == value: #노드의 값이 내가 찾는 값이라면
return node
else: # 해당 노드가 내가 가진 값을 안 갖고 있다면
node = node.next # 다음 노드로 'node'를 변경.
근데 딜릿할 때 제가 지우려는 value가 여러 노드에 있으면, 연결 리스트니까 맨 앞에 있는 노드가 제거되는 것으로 이해했는데 맞나요?
이런 질문을 했었는데 맞다고 답변 받았다. 오늘 도전과제는 개인적으로 준비해야할 급한게 있어서 다음을 기약..