Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 부트캠프
- 웹스크래핑
- 관계형데이터베이스
- WEB
- 클라우드
- 데브코스
- Django
- 운영체제
- 데이터웨어하우스
- airflow
- AWS
- 에어플로우
- 파이썬
- 웹크롤링
- DataWarehouse
- 웹자동화
- 알고리즘
- 프로그래머스
- 데이터베이스
- Amazon
- Service
- 개발
- 취준
- 자료구조
- SQL
- 데이터엔지니어
- CS
- 개념정리
- 기술면접
- 데이터엔지니어링
Archives
- Today
- Total
사진과 음악을 좋아하는 개발자 지망생의 블로그
스택(Stack), 큐(Queue), 힙(heap), 우선순위 큐(Priority Queue), heapq 본문
CS/자료구조 & 알고리즘
스택(Stack), 큐(Queue), 힙(heap), 우선순위 큐(Priority Queue), heapq
devculture309 2024. 12. 16. 09:57반응형
1. 스택(Stack)
- 시간 순서상 가장 최근에 추가한 데이터가 가장 먼저 출력되는 후입선출(LIFO, Last In First Out) 형식으로 데이터를 다루는 자료구조
- 데이터를 추가하는 것을 push, 데이터를 꺼내는 것을 pop이라고 한다
- 활용 예시: 후위 표기법 연산, 괄호 유효성 검사, 웹 브라우저 방문기록(뒤로 가기), 깊이우선탐색(DFS)
1) 리스트(list) 기반 구현
- python에서 stack을 가장 간단히 구현하는 방법은 list를 이용하는 것이다.
- append 메소드를 사용하면 맨 뒤에 데이터가 추가 되고, pop을 하게 되면, 맨 앞의 데이터가 나온다
- 시간복잡도: $O(1)$
# stack 선언
s = []
# push - O(1)
s.append(1) # [1]
s.append(2) # [1, 2]
s.append(3) # [1, 2, 3]
# pop - O(1)
s.pop() # [1, 2]
s.pop() # [1]
2. 큐(Queue)
- 먼저 저장한 데이터가 먼저 출력되는 선입선출(FIFO, First In First Out)형식으로 데이터를 다루는 자료구조
- 데이터를 추가하는 것을 enqueue라고 하고, 데이터를 꺼내는 것을 dequeue라고 한다
- 시간복잡도
- Enqueue: $O(1)$
- Dequeue: $O(1)$
1) deque (덱)기반 구현
- Double-eneded Queue
- 양 끝에 추가/삭제를 지원하며 Python에서 Queue를 구현할 수 있는 자료구조
append
메소드를 사용하면 맨 뒤에 데이터가 추가 되고, popleft을 하게 되면, 맨 뒤의 데이터가 나온다 -> O(1)- 활용 예시: Cache구현, 프로세스 관리, 너비우선탐색(BFS) 등
deque (덱) 내부 구조
typedef struct BLOCK {
struct BLOCK *leftlink;
PyObject *data[BLOCKLEN];
struct BLOCK *rightlink;
} block;
typedef struct {
PyObject_VAR_HEAD
block *leftblock;
block *rightblock;
Py_ssize_t leftindex; /* 0 <= leftindex < BLOCKLEN */
Py_ssize_t rightindex; /* 0 <= rightindex < BLOCKLEN */
size_t state; /* incremented whenever the indices move */
Py_ssize_t maxlen;
PyObject *weakreflist;
} dequeobject;
- 블락 단위로 묶인 이중 연결 리스트
- deque는 단순히 데이터를 하나씩 연결한 구조가 아닌 64개의 데이터를 담을 수 있는 블락들을 이중 연결 리스트로 연결한 구조
- C언어로 구현된 블락의 구조
- 각 블락은 leftlink와 rightlink라는 포인터를 통해 이전 블락과 다음 블락을 가리킴
- 또한, 각 블락에는 64개의 데이터를 담을 수 있는 배열이 존재하며, 이를 통해 데이터를 저장함
- dequeobject 구조
- leftblock과 rightblock이라는 포인터 두 개를 가리키며, 데이터의 삽입 및 삭제가 여기에서 일어남
- leftindex와 rightindex라는 두 변수가 있어, 각 블락 내에서 실제로 데이터가 추가되거나 삭제되는 위치를 가리킴
- link와 block의 차이
- leftlink와 rightlink는 현재 블락에서 그 이전/다음 블락을 가리키는 포인터
- deque -> [Block 1] <-> [Block 2] <-> [Block 3] ^ ^ leftlink rightlink
- leftblock와 rightblock은 deque의 가장 왼쪽/오른쪽에 있는 블락을 나타내는 포인터
- leftblock -> Block 1 <-> Block 2 <-> Block 3 <- rightblock
초기화
from collections import deque
# 비어있는 큐 만들기
que = deque()
# 원소가 있는 큐 만들기
que = deque([1, 2, 3])
데이터 추가
- 왼쪽 끝에 새로운 값 을 추가:
appendleft()
- 오르쪽 끝에 새로운 값을 추가:
append()
from collections import deque
que = deque()
que.append(3)
que.append(6)
que.append(9)
que.append(12)
print(que) # deque([3, 6, 9, 12])
que = deque()
que.appendleft(3)
que.appendleft(6)
que.appendleft(9)
que.appendleft(12)
print(que) # deque([12, 9, 6, 3])
데이터 추출
from collections import deque
que = deque([1, 2, 3])
while que:
print([que.popleft())
# 1
# 2
# 3
que = deque([1, 2, 3])
while que:
print([que.pop())
# 3
# 2
# 1
주요 연산 시간복잡도
Operation | Average Case | Amortized Worst Case |
---|---|---|
Copy | $O(n)$ | $O(n)$ |
append | $O(1)$ | $O(1)$ |
appendleft | $O(1)$ | $O(1)$ |
pop | $O(1)$ | $O(1)$ |
popleft | $O(1)$ | $O(1)$ |
extend | $O(k)$ | $O(k)$ |
extendleft | $O(k)$ | $O(k)$ |
rotate | $O(k)$ | $O(k)$ |
remove | $O(n)$ | $O(n)$ |
Get Length | $O(1)$ | $O(1)$ |
- $k$: 회전수 |
appendleft 작동 원리
- 비어있는 블락
- 초기 상태
- 이때
deque
는 내부적으로 하나의 블락을 할당하고 있음 - 초기 상태에서
leftindex
는 블락의 끝(BLOCKLEN)인 64를 가리키고 있고,rightindex
는 블락의 시작(0)을 가리키고 있음
- 이때
appendleft
호출- 예를 들어,
appendleft(10)
을 호출하면,leftindex
를 하나 줄인 후(leftindex--
), 그 자리에10
을 넣음 - 이제
leftindex
는 63을 가리키게 되고,10
은 블락의 맨 끝에서 한 칸 앞에 저장됨 Block (64 slots): [_, _, _, ..., 10] leftindex: 63 rightindex: 0
- 예를 들어,
appendleft
재호출- 예를 들어,
appendleft(20)
을 호출하면, 마찬가지로leftindex
를 하나 줄인 후(leftindex--
), 그 자리에20
을 넣음 - 이제
leftindex
는 62를 가리키게 되고,20
은10
앞에 저장함
- 예를 들어,
- 초기 상태
- 공간이 있는 블락
- 초기 상태
- 한 블락안에 데이터가 저장되어 있으나 추가로 데이터를 저장할 수 있음
deque([10, 20, 30])
가 있고, 한 블락 안에 저장되어 있다고 가정해 보자- 블락의 크기는 64이지만, 현재는 3개의 데이터만 저장되어 있고,
leftindex
는 61,rightindex
는 63을 가리키고 있음
appendleft
호출- 예를 들어,
appendleft(5)
를 호출하면,leftindex
를 하나 줄이고(leftindex--
), 그 자리에 5를 저장함 Block (64 slots): [..., _, _, 5, 10, 20, 30] leftindex: 60 rightindex: 63
- 예를 들어,
appendleft
재호출- 예를 들어,
appendleft(1)
을 호출하면, 마찬가지로leftindex
를 하나 줄여서 그 자리에 1을 저장합니다. Block (64 slots): [..., _, 1, 5, 10, 20, 30] leftindex: 59 rightindex: 63
- 예를 들어,
- 초기 상태
- 블락이 가득 찼을 때
- 초기 상태
appendleft()
를 여러 번 호출하여, 블락이 가득 차게 되면, 이 블락에는 더 이상 데이터를 추가할 수 없음Block 1 (64 slots): [64, 63, 62, ..., 1] leftindex: 0 rightindex: 63
- 이제 새로운 데이터를
appendleft()
하려면 새로운 블락을 생성해야 함
appendleft
호출- 예를 들어,
appendleft(65)
를 호출하면 새로운 블락이 생성되고, 새 블락의 첫 번째 자리에65
가 추가됨 New Block 2 (64 slots): [65, _, _, _, _, ..., _] (이 블락에 데이터가 추가됨) Block 1 (64 slots): [64, 63, 62, ..., 1] (이전 블락은 그대로 유지됨)
- 새 블락은 이전 블락과 이중 연결 리스트로 연결됨
- 새 블락의
rightlink
는 기존 블락을 가리키고, 기존 블락의leftlink
는 새 블락을 가리킴
- 예를 들어,
- 초기 상태
3. 우선순위 큐(Priority Queue)
1) heap
정의
- 요소의 우선순위를 매기고 관리하는 대표적인 알고리즘
- 완전 이진 트리 형태로 루트 노드가 언제나 최대값/최소값을 가짐 → 최대 힙(max heap), 최소 힙(min heap)
- min heap: 자식 노드의 값이 부모 노드의 값보다 크며 이 속성이 모든 노드에 재귀적으로 적용된 트리 형태의 자료구조
- max heap: 자식 노드의 값이 부모 노드의 값보다 작으며 이 속성이 모든 노드에 재귀적으로 적용된 트리 형태의 자료구조
- 서브트리보다 항상 큰 키를 갖고으며 위로 올라갈수록 큰 값을 가지지만, 왼쪽 서브트리와 오른쪽 서브트리 간 관계가 정의 되어있지 않음
최대 힙의 원소 삽입
- 트리의 마지막 자리에 새로운 원소를 임시로 저장
- 부모 노드와 키 값을 비교하며 자신 보다 큰 값을 가진 부모 노드를 만나기 전 까지 위로 이동 ⇒ 시간복잡도: $O({\log}n)$
최대 힙의 원소 삭제
- 루트 노드의 제거
- 트리 마지막 자리 노드를 임시로 루트 노드의 자리에 배치
- 자식 노드들과의 값 비교로 자신 보다 작은 값을 가진 자식 노드를 만나기 전 까지 아래로 이동⇒ 시간복잡도: $O({\log}n)$
→ 자식 노드가 두 개면 더 큰 키값을 가진 쪽으로 이동
2) 우선순위 큐(Priority Queue)
- 힙을 큐 형태로 구현한 자료구조
- 큐(Queue)가 FIFO(선입선출) 방식을 따르지 않고 원소들의 갖고 있는 우선순위에 따라 나오는 자료구조
- 대표적인 예로 '운영체제의 CPU 스케쥴러'가 있다
- 우선순위 큐는 두가지 방식으로 구현 가능하다
- Enqueue 시 우선순위를 유지하도록 한다
- Dequeue 시 우선순위를 유지하도록 한다
- 위 두가지 방식 중 'Enqueue 시 우선순위를 유지' 가 미세하게 유리하다
- 왜냐하면 Dequeue 시 우선순위가 높은 것을 선택하도록 할 경우 모든 데이터에 대하여 다 살펴봐야하기 때문
3) heapq
- 파이썬에서 제공하는 힙(Heap) 기반의 우선순위 큐(priority queue)를 구현한 모듈
- 최소 힙(min-heap)을 기본적으로 사용하여, 우선순위가 가장 작은 값이 가장 먼저 처리되도록 구현되어 있음
주요 기능
heappush(heap, item)
item
을 힙에 추가합니다- 힙 구조를 유지하면서 새로운 요소가 적절한 위치에 추가됨
- 시간 복잡도: O(log N) (N은 힙에 있는 요소의 개수)
heappop(heap)
- 힙에서 가장 작은 요소를 제거하고 반환함
- 힙 특성을 유지하면서 재정렬됨
- 시간 복잡도: O(log N)
heapify(iterable)
- 리스트를 힙으로 변환함
- 주어진 iterable(리스트)를 힙 구조로 바꾸어줌
- 시간 복잡도: O(N) (N은 iterable의 길이)
heappushpop(heap, item)
- item을 힙에 추가하고, 바로 가장 작은 요소를 제거
heappush()
와heappop()
을 함께 수행하는 것과 유사하지만, 힙을 재정렬하는 횟수가 줄어듬- 시간 복잡도: O(log N)
heapreplace(heap, item)
:- 힙에서 가장 작은 요소를 제거하고, 그 자리에 새로운 item을 추가
- 이는
heappop()
과heappush()
를 함께 수행하는 것과 비슷하지만, 힙 재정렬이 한 번만 이루어짐 - 시간 복잡도: O(log N)
주요 시간 복잡도
Operation | Average Case |
---|---|
heapify(iterable) |
$O(N)$ |
heappush(heap, item) |
$O(\log N)$ |
heappop(heap) |
$O(\log N)$ |
heappushpop(heap, item) |
$O(\log N)$ |
heapreplace(heap, item) |
$O(\log N)$ |
반응형
'CS > 자료구조 & 알고리즘' 카테고리의 다른 글
카운팅 정렬(Counting Sort), 기수 정렬(Radix Sort) (0) | 2024.12.18 |
---|---|
정렬(선택 정렬, 삽입 정렬, 버블 정렬, 퀵 정렬, 병합 정렬, Tim sort) (0) | 2024.12.17 |
연결리스트 투 포인터(런너) 기법, Reverse Linked List (0) | 2024.12.13 |
배열 or 리스트(정적 배열, 동적 배열, doubling, 분할 상환 시간 복잡도), 연결리스트(Linked List), 파이썬 리스트(특징, 메모리 구조, 주요 연산 시간 복잡도) (0) | 2024.12.12 |
메모리 구조와 자료 구조 간 힙(Heaps)의 차이 (0) | 2023.07.12 |