✌️스택과의 가장 큰 차이점,✌️ 스택은 순서를 무시하고 나중에 들어온 애를 먼저 처리한다.
✌️스택과의 차이점✌️은, 스택은 한 쪽(top)에서만 입출력이 가능하고, 큐는 입출력이 양 쪽(입력은 rear로, 출력은 front에서)에서 따로 처리된다는 점이다.
__init__
)no
: 큐에 들어있는 데이터의 개수 (= 노드 개수)
front
: 큐에서 가장 오래 전에 추가된 데이터가 들어있는, 삭제가 용이한 front 노드에 대한 참조
rear
: 큐의 가장 최근에 추가된 데이터가 들어있는, 새로운 데이터 삽입이 용이한 rear 노드에 대한 참조
__init__()
: 생성자
__len__()
: 큐의 크기를 리턴
is_empty()
: 큐가 비어있다면 True 리턴
✌️스택과의 차이점✌️은, 스택은 입출력이 모두 top에서만 이루어지므로 front
, rear
대신에 top
속성 하나만으로 구현이 가능하다는 것이다.
@D2Coding
enqueue()
)enqueue()
: # O(1)front
와 rear
모두 같은 노드를 참조rear
갱신dequeue()
)dequeue()
: # O(1)peek()
)peek()
: O(1)print()
)print()
:front
, rear
이 있고 없고의 차이clear()
)clear()
:
clear
가 없다고 하는 것인 지 모르겠다.😥Problem.
pop(0)
연산의 시간복잡도가 O(N)이다.Solution.
덱 자료구조(연결 리스트의 일종)
덱은 노드들이 2중으로 연결되어 있다.
덱은 양방향 모두 삽입/삭제가 O(1)이다.
popleft()
연산을 이용하면 O(1)에 프런트 데이터를 삭제할 수 있다.
덱의 실생활에서의 쓰임:
웹 페이지 스크롤
스크롤을 내리면 페이지 상단의 정보는 삭제되고, 페이지 하단의 정보가 새롭게 삽입되는 것의 연속이다.
✌️연결리스트와 덱의 차이✌️는 단방향, 양방향이다.
collections
라이브러리를 다운받아 사용할 수 있다. append()
함수를 사용해 데이터를 삽입하는 enqueue()
메서드를 구현한다.popleft()
메소드를 사용할 수 있다. pop()
과 비슷하지만 왼쪽(주로 front 노드가 위치한)의 요소를 삭제하는 기능이 있다.peek()
메소드는 리스트와 마찬가지로 슬라이싱을 통해 구할 수 있다.print()
를 사용해서 덱의 모든 데이터를 출력할 수 있다. deque([1, 2])
와 같이 출력된다.
"""
N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다.
이제 다음과 같은 동작을 카드가 한 장 남을 때까지 반복하게 된다.
우선, 제일 위에 있는 카드를 바닥에 버린다. 그 다음, 제일 위에 있는 카드를 제일 아래에 있는 카드 밑으로 옮긴다.
예를 들어 N=4인 경우를 생각해 보자. 카드는 제일 위에서부터 1234 의 순서로 놓여있다. 1을 버리면 234가 남는다. 여기서 2를 제일 아래로 옮기면 342가 된다. 3을 버리면 42가 되고, 4를 밑으로 옮기면 24가 된다. 마지막으로 2를 버리고 나면, 버린 카드들은 순서대로 1 3 2가 되고, 남는 카드는 4가 된다.
N이 주어졌을 때 마지막에 남게 되는 카드를 출력하는 프로그램을 작성하시오.
"""
✏️ 풀이: deque자료형을 이용해 삽입,삭제,출력 연산을 수행한다.
삽입은append()
, 삭제는popleft()
, 출력은print()
, front 노드 데이터 확인은deque[0]
슬라이싱으로 구현이 가능하다.
from collections import deque
먼저 파이썬 deque 자료형을 사용할 수 있는 collections 라이브러리를 가져와 deque를
import
한다.
n = 5
디버깅을 위해 작성해둔 것으로 무시해도 된다.
💡함수를 작성할 때에는 중간중간 확인이 어렵기 때문에 처음부터 def
안에 코드를 작성하기보다는 밖에 작성하고, 입력값에 직접 testcase를 지정해서 테스트해보는 것이 좋다.
def dis_card(n:int) -> Any:
함수 이름은 '버리다'의 뜻을 가지고있는 discard로 언어 유희를 해보았다. card를 discard하다.🤣 입력값은 카드의 장 수인
int
형식의n
이다.-> Any
는 출력값의 형식이 어떤 것이든 상관없다는 뜻이다.
c = deque()
card의 앞 자를 따서
c
라는 이름의 인스턴스를 생성해주었다. 변수(인스턴스)c
에 빈 덱을 저장했다.
for i in range(1,n+1): c.append(i)
빈 덱
c
에 1부터n
까지의 숫자를append
해주었다. 이제c
에는deque([1,2,3,4,...n])
과 같이 데이터가 들어있게 된다.
while len(c) > 1: c.popleft() c.append(c.popleft())
카드가 1장이 될 때까지
while True
문으로 반복한다.
popleft()
함수를 활용해서c
의 front에 위치한 데이터를 하나 삭제해준다.
💡이 때 c.popleft()
는 삭제만 하지만, 삭제한 데이터를 return
하기 때문에 print(c.popleft())
또는 data = c.popleft()
와 같이 삭제된 데이터를 출력하거나 다른 변수에 넣어 살릴 수 있다.
print(c[0])
슬라이싱을 통해 front에 위치한
c
의 데이터를print()
문으로 출력할 수 있다. 마지막 남은 하나의 데이터를 출력한다는 의미이다.
"""
어느 왕국의 이웃 나라 외동딸 공주가 숲 속의 괴물에게 잡혀갔습니다.
왕국에는 왕자가 N명이 있는데 서로 공주를 구하러 가겠다고 합니다. 왕국의 왕은 다음과 같은 방법으로 공주를 구하러 갈 왕자를 결정하기로 했습니다.
왕은 왕자들을 나이 순으로 1번부터 N번까지 차례로 번호를 매기고 1번 왕자부터 N번 왕자까지 순서대로 시계 방향으로 돌아가게 동그랗게 앉게 합니다. 그리고 1번 왕자부터 시계 방향으로 돌아가며 1부터 시작하여 번호를 외치게 합니다. 한 왕자가 K(특정 숫자)를 외치면 그 왕자는 공주를 구하러 가는데서 제외되고 원 밖으로 나오게 됩니다. 그리고 다음 왕자부터 다시 1부터 시작하여 번호를 외칩니다.
이렇게 해서 마지막까지 남은 왕자가 공주를 구하러 갈 수 있습니다. 마지막에 남은 왕자의 번호는 무엇인가요?
n과 k가 주어 졌을 때, 마지막에 남은 왕자의 번호를 리턴하는 함수를 작성하시오.
"""
✏️ 풀이: 이 문제는 큐로 푸는 문제이다. 큐를 동그랗게 말아서 시작과 끝이 만나게 한 형태로 이해할 수 있다. 큐는 front에서만 삭제가 가능하므로 원탁에
N
개의 의자를 두고, 왕자를 시계방향으로K
번 돌게 하면 1번 의자(front)에 앉은 왕자를 빼는 문제로 재정의할 수 있다.
def save_princess(n: int, k: int) -> int:
공주 구하기 대작전이라 함수명을
save_princess
로 했는데 직관적이고 잘 만든 것 같다. 교수님 코드도save_princess
였다.😊 입력값은n
과k
로 두 개이며 모두 정수형으로 지정해줬다. 출력값도 정수형(integer)이다.
p = LinkedListQueue()
앞서 만들어 놓은 연결리스트로 구현한 큐 코드를 사용하였다. 공주를 구하러 갈 왕자도 prince, p로 시작하므로 인스턴스 명을
p
로 지정했다. 이제 이p
가LinkedListQueue()
클래스 내의 모든self
자리에 들어가 작동할 것이다.😊
for i in range(1,n+1): p.enqueue()
앞서 카드 버리기 문제에서는
append()
함수를 사용해 삽입을 해주었는데, 이 경우 클래스에서 이미 구현한enqueue()
메소드가 있기 때문에 사용해 주었다.for
문을 활용해 1부터n
까지의 숫자를 순서대로p
에 입력해주었다. 이는 왕자의 id로서의 역할을 한다.
while len(p) > 1:
앞서 카드 버리기와 마찬가지로 한명만 남을 때까지
while True
문을 통해 반복적으로 왕자들의 자리를 바꿔줄 것이다.
for _ in range(k-1): p.enqueue(p.dequeue()) p.dequeue()
for
문에서_
를 넣는 경우는 단순 반복일 때이다.k
-1번 반복적으로 front 자리에 앉을 왕자의 id를 바꾸어줄 것이다. 왕자가 모두 반시계 방향으로 돌면서 한 칸씩 자리를 옮기는 것은 곧 맨 앞(front)의 왕자가 맨 뒤(rear)으로 간다는 의미이다. 이렇게 되면 2번 왕자가 front에 가는 것과 같다. 참조 구현하는 방법은 디큐(맨 앞의 왕자 삭제)와 인큐(맨 뒷자리에 삭제한 왕자 삽입)를 반복하는 것이다.k
-1번 반복하면k
인 왕자가 front에 앉게된다. 이 때for
문에서 빠져나오게 되는데dequeue()
메소드를 사용해주면 front에 있는 애를 바로 삭제해줄 수 있다.
💡k
번 반복이 아닌 k
-1번 반복인 이유는?!
왕자가 3명이라고 했을 때, 한 번 디큐-인큐를 반복하면 2번 왕자가 front 자리에 가기 때문이다. 두 번째로 반복하면 3번 왕자가 front에 간다. k
번째 반복하면 k
+1번째 왕자가 front에 가는 것임을 알 수 있다. 따라서 k
번째 왕자가 front에 오는 시점은 k
-1.
return p.peek()
front의 데이터를 출력해주는
peek()
메서드를 통해return
으로p
의 마지막 남은 하나의 데이터를 반환할 수 있다.