자료구조 - Songwooseok123/Study_Space GitHub Wiki

코딩 테스트 대비 자료구조 이론 정리

리스트 요소 추가/삭제 복잡도 헷갈리는거

a = [1,2,3]

  • a.append(x): O(1)
  • a.pop() : O(1) -> pop은 기본적으로 맨 마지막꺼 빼냄
  • 근데 list에서 a.pop(0) 이런 특정 index를 지정하는 건 복잡도 O(n)임 -> 복사후 index 한칸씩 땡겨야 되서
  • sum,max,min, remove, replace는 복잡도 O(n)

스택

  • stask(선입후출) LIFO
  • stack은 걍 리스트로구현,
  • stack 문제는 pop을 잘 활용해야함.
  • 불필요한 for문을 사용해서 탐색하는 것(복잡도 *n 되는것) 보단 마지막 원소만 지우는 것(복잡도 o(1))이 훨씬 이득

문제 종류

  • 4949번: 균형잡힌 세상 - 문자열에서 "( )", "[ ]"이 균형을 이루는지

  • queue(선입선출, 즉 대기줄) FIFO
  • queue는 collections의 deque로 구현
  • deque(), popleft()
from collections import deque
ddd= deque()
ddd.append(5)
print(ddd)
ddd.append(4)
print(ddd)
ddd.append(3)
print(ddd)
ddd.popleft()
print(ddd)
'''
deque([5])
deque([5, 4])
deque([5, 4, 3])
deque([4, 3])
'''
d= deque(range(1,int(input())+1))

문제 종류

  • 18258번: 큐에 원소 추가, pop 구현 문제

우선순위 큐(Priority Queue)

  • 가장 큰 원소 혹은 가장 작은 원소를 반복적으로, 효율적으로 뽑아야할 때 사용

문제 종류

  • 1715번: 10,20,40의 카드더미 3장이 있는데, 어떻게 정렬해야 OMP(Optimal Mege Pattern)이 가장 작을까?
    • 허프만 코딩의 원리에 따라 더미에서 가장 작은 2개를 계속 선택해 나가는 방법
  • 1417번: 투표하는데,단독 우승하려면, 나머지 후보들에게서 뺏어와야하는 표 수 구하기 (가장 큰애를 골라서 뺏어와야 겠네)
    • (5,7,7) 이면 나머지 두명한테 한표씩 뺏어와서 (6,5,5)로 만들 수 있음.

특징

  • 가장 작은 원소 먼저 뺌
  • 원소 넣고 빼는데는 logN 복잡도(heapq 쓰던, from queue import PriorityQueue 쓰던)

🔸 heapq

import heapq

heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)

print(heapq.heappop(heap))  # 출력: 1

🔹 queue.PriorityQueue

from queue import PriorityQueue

pq = PriorityQueue()
pq.put(3)
pq.put(1)
pq.put(2)

print(pq.get())  # 출력: 1
print(pq) # 하면 <queue.PriorityQueue object at 0x000001AB...> ← 우리가 기대하는 리스트 아님!
pq.empty() # False
pq.qsize() # 길이 

백준 1715번 문제를 풀다가 "우선 순위 큐(Priority Queue)"에 대해 알게 되었고 우선순위 큐를 구현하기 위해 파이썬의 heapq 모듈 사용 중 heapq.heappush 하는 과정이 이해가 가지 않아서 이를 계기로 '나동빈'님의 영상을 보게 되었습니다.

 

import heapq #heap 라이브러리 import
heap_list = []
iterable =[3,5,9,6,4]

for value in iterable : heapq.heappush(heap_list,value) #heap 라이브러리의 heappush 함수를 이용해서 iterable의 원소를 heap_list에 추가 print(heap_list)

 

위의 코드는 파이썬에서 heap 라이브러리를 import하고 heapq.heappush 함수를 이용해서

iterable이라는 이름의 list의 원소를 하나씩 heap_list에 추가하는 코드입니다. 

 

코드의 출력으로

[3]

[3, 5]

[3, 5, 9]

[3, 5, 9, 6]

[3, 4, 9, 6, 5]

다음과 같이 나오게 되는데, 결과 5번째 줄에 '원소 4'가 추가 될 때 '원소 5'와 순서가 바뀌는 것을 보고 heap 에 데이터를 추가할 때 따르는 규칙에 대해 궁금하게 되어 '나동빈' 님의 영상을 찾아보게 되었습니다. 

 

이 게시글은 유튜브 '동빈나' 님의 영상(https://www.youtube.com/watch?v=AjFlp951nz0)을 바탕으로 공부한 것을 정리한 내용입니다.

 

 

우선 순위 큐(Priority Queue)를 설명하기 앞서 먼저 3가지 자료구조에 대해 알아보도록 하겠습니다. 

 

자료구조 : 추출되는 데이터 

- 1. Stack : 가장 나중에 삽입된 데이터부터 추출(LIFO 구조 : Last In First Out) 

- 2. Queue : 가장 먼저 삽입된 데이터(FIFO 구조 : First In First Out)  ->파이썬에선 보통 'deque' 를 사용해서 구현

- 3. Priority Queue : 가장 우선 순위가 높은 데이터 

 

이 중에서 3. Priority Queue를 구현하는 방법 2가지와 시간 복잡도에 대해 알아 보겠습니다. 

 

Priority Queue를 구현 하는 방법

PQ 구현 방식 삽입 시간 삭제 시간
List O(1) O(N)
Heap O(logN) O(logN)

 

  •  Heap 정렬 :  N개의 데이터를 heap에 넣었다가 꺼내는 작업 -> O(NlogN)

 

정리 :

"Priority Queue를 구현하는 방법으로 Heap 모듈을 이용할 수 있다"

"데이터를 추가,삭제할 때 최악의 경우에도 시간복잡도 O(logN)로 힙의 성질을 유지할 수 있다는 장점이 있다. "

 

그렇다면 'Heap'은 뭘까?


Heap : 완전 이진 트리 자료구조의 일종 -> 항상 root node(트리에서 가장 위에 있는 노드)를 제거

- heap에는 min heap과 max heap 2가지 종류가 있는데 그 중에서 min heap에 대해서 자세히 다뤄보도록 하겠습니다. 

  • Max heap : 루트 노드가 가장 큰 값을 가짐
  • Min heap : 루트 노드가 가장 작은 값을 가짐  
Min heap 트리 구조

트리에서 상위에 있는 노드를 부모노드, 하위에 있는 노드를 자식 노드라고 합니다. 

원소 3은 5와 9의 부모 노드이며 , min heap은 부모노드가 항상 자식노드보다 작은 것을 만족시킵니다. 

 

Heap의 구조에 대해서 알아보니, 처음에 궁금했던 삽입 순서에 대한 이해가 가기 시작했습니다. 

[3,5,9,6] 으로만 이뤄져 있던 트리에 원소 4를 넣으니 min heap의 성질을 만족 시키기 위해(4는 5보다 작으니 상위로 올라가야겠다! ) 원소 4가 부모노드로 올라가는 것입니다. 밀려난 원소(5)는 원소 4가 있던 자식 노드로 자리가 바뀝니다. 

heap에 원소를 집어 넣을 때 규칙을 알아보았습니다. heap에서 원소를 하나씩 꺼낼 때는 마찬가지로 min heap의 성질을 만족시켜야 합니다

밑의 코드와 결과를 보면 heapq.pop 함수를 통해서 가장 작은 원소부터 꺼내는 것을 확인 할 수 있습니다. 

 

import heapq
heap_list = []
iterable =[3,5,9,6,4]

for value in iterable : heapq.heappush(heap_list,value) print(heap_list)

print("") print("heapq.heappop함수를 이용해 데이터를 하나씩 꺼내보자")

result =[] for i in range(len(heap_list)): result.append(heapq.heappop(heap_list)) print(result)

[3]

[3, 5]

[3, 5, 9]

[3, 5, 9, 6]

[3, 4, 9, 6, 5]

 

heapq.heappop함수를 이용해 데이터를 하나씩 꺼내보자

[3]

[3, 4]

[3, 4, 5]

[3, 4, 5, 6]

[3, 4, 5, 6, 9]

 

 

요약 

 

여태까지 우선순위 큐 자료구조와 이를 구현하기 위해 파이썬에서 사용할 수 있는 Heap 모듈에 대해서 알아 보았습니다.

최소값, 최대값을 계속 호출할 때 사용하는 우선순위 큐 자료구조를 구현할 때, heap 모듈을 사용하면 데이터를 추가,삭제할 때 최악의 경우에도 시간복잡도 O(logN)로 힙의 성질을 유지할 수 있다는 장점이 있습니다. 

 

감사합니다. 

Hash 자료 구조

문제 종류

  • 3033번: 문자열 주어지고, 2번이상 반복되는 문자열 중에 가장 긴거 구하기
    • 문자열이 반복되는지 구하기 위해서 hash 값으로 비교한다.
  • 5525번: 문자열 주어지고, 특정 문자열이 몇 번 등장하는지 구하기(hash로 풀 수 있지만, 시간초과나서 다른 방법이 더 나음)

문자열 hash: 소문자면 26개니까 적당히 큰 31진법으로 바꾼다.

특징

  1. 같은 문자열은 해쉬값이 같다.
  2. 다른 문자열은 해쉬값이 같을 수 있다 -> 다른 문자열을 해쉬값이 다르도록 31진법으로 바꾸는거임. 만약에 대문자 포함한다면 52 이상 진법으로 바꿔야 겠지
  • 근데 문자열이 길면 값이 너무 커지니까 매 자리수마다 특정 수(1e9 +7)로 나눠서 나머지를 취한다.
  • 각 구간마다 해쉬값을 새로 구하는게 아니라, 전의 해쉬값을 활용해서 다음 해쉬값을 효율적으로 구한다. 밑의 그림 참고
  1. 비교 연산이 빠르다. 만약에 n짜리 길이 2개 문자열을 비교하려면 n번 비교해서 같은지 판단해야되는데, hash 구해놓으면 걍 정수 2개 비교하면 끝.

image

코딩 순서:

  1. 해쉬 계산을 위한 준비물(모드, 파워)
  2. 첫 해쉬값 구하기
  3. 첫 해쉬값을 기반으로 구간 돌면서 hash값 업데이트 하기
  • hash 값을 업데이트 할 땐 , 항상 커지면 mod로 나눈다. 음수가 될 거 같으면 mod를 더한다.

누적합 배열 자료구조

문제 종류:

  • 14465: 정상적으로 작동하는 연속 k개의 신호등이 존재하라면 최소 몇개의 신호등을 수리해야 하는지
  • 2559: 연속적인 며칠 k일 동안의 온도의 합이 가장 큰 값을 계산

특징

  • 배열의 연속한 구간 합을 구할 때
  • 그냥 리스트의 sum은 o(n) 복잡도를 가짐.
    • 구간합을 1억 번 구하라그러면 1억번 *n
    • 누적합을 한 번만 구해놓으면, 누적합 구하는 복잡도 + 1억번 *1
  • a[3]~a[4] 합은 psum[4] - psum[2]

트리 자료 구조

문제 종류

  • 1068: 부모 배열이 주어진다. 만약 k노드를 지웠을 때, 리프 노드의 개수를 구하라.
  • 14267: 부모(직장 상사) 배열이 주어진다. 상사로부터 칭찬받은 직원과 칭찬의 정도가 주어진다. 칭찬은 내리사랑으로 이뤄진다(칭찬 받은 부하는 그칭찬 정도만큼 그의 부하한테 칭찬해줌) . 이럴 때 직원들의 칭찬 받은 정도 구하기

특징

  • 선: 간선/엣지, 점: 노드
  • 위로 연결되어 있는 노드들이 모두 조상노드
    • 바로 위에 연결되어 있는건 부모노드
  • 밑에 연결되어 있는 모든 노드들이 자손노드
    • 바로 밑에 연결되어 있는 노드가 자식노드
  • 맨 위에 있는게 루트 노드, 맨 밑에 있는게 leaf 노드

트리를 표현하는 방법

  1. 부모 노드가 뭔지를 저장
  2. 자식 노드가 뭔지를 저장

그래프 자료 구조

문제 종류

  • 2606: k 컴퓨터가 바이러스에 걸렸을 때, 바이러스에 걸리게 되는 컴퓨터의 총 수 구하기
  • 5567: 친구의 친구까지 결혼식 초대 할 때 총 몇명 초대하는지

image

  • 인접리스트 (인접한 노드로 구성된 배열)
    • 간선이 있는지 없는지 고려

hashmap

- hashtable이라고도 불리고, 파이썬의 dictionary또한 hashtable로 구현되어있다. key와 value 쌍으로 데이터를 저장하는 자료구조를 말한다.

Class

- class는 속성과 method로 이루어져 있음. - init 생성자 : 인스턴스를 만들 때 가장먼저 호출되는 method
class semiGPT(nn.Module):
    def __init__(self, vocab_length): # init 생성자 
        super().__init__()
        self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)

    def forward(self, inputs, targets=None):
        logits = self.embedding_token_table(inputs)
        if targets is None:
            loss = None
        else:
            batch, seq_length, vocab_length = logits.shape
            logits = logits.view(batch * seq_length, vocab_length)
            targets = targets.view(batch*seq_length)
            loss = F.cross_entropy(logits, targets)
        return logits, loss

    def generate(self, inputs, max_new_tokens):
        for _ in range(max_new_tokens):
            logits, loss = self.forward(inputs)
            logits = logits[:, -1, :]
            probs = F.softmax(logits, dim=-1)
            next_inputs = torch.multinomial(probs, num_samples=1)
            inputs = torch.cat((inputs, next_inputs), dim=1)
        return inputs

model = semiGPT(ko_vocab_size).to(device) # 여기서 model이 instance임 ("semiGPT 클래스로부터 만들어진 model 인스턴스"라고 표현함) 
  • model이라는 instance를 만들고,
  • model.generate()라는 method를 사용
  • super 부분은 부모 클래스의 init 메서드를 호출하는 것이다.
  • super는 물려받은 부모 클래스의 메서드에 접근할 수 있게 해줌
  • super().init() 호출은 nn.Module(부모)의 생성자(부모클래스의 init에 있는 속성들)를 호출한다. 이는 nn.Module의 모든 기능(method)과 속성을 제대로 초기화하고 사용할 수 있게 하는 단계임.
    • 부모 class를 상속하고서 method를 오버라이드 할 수도 있음.
## 부모 클래스
class Monster:
    def __init__(self, name, health, attack):
        self.name = name
        self.health = health
        self.attack = attack

    def move(self):
        print(f"[{self.name}] 지상에서 이동하기")


## 자식 클래스
class Wolf(Monster): # 상속을 받았기 때문에 생성자를 생략함
    pass


class Shark(Monster):
    def move(self):  # 메서드 오버라이딩
        print(f"[{self.name}] 헤엄치기")


class Dragon(Monster):
    def move(self):  # 메서드 오버라이딩
        print(f"[{self.name}] 날기")


wolf = Wolf("울프", 1500, 200)
wolf.move()

shark = Shark("샤크", 3000, 400)
shark.move()

dragon = Dragon("드래곤", 8000, 800)
dragon.move()

결과

[울프] 지상에서 이동하기
[샤크] 헤엄치기
[드래곤] 날기
  • 파이썬에서 자료형도 클래스다
a= 10
b= 'aaa'
print(b.__dir__()) 
``` -> class의 메서드를 확인할 수 있다.

</details>


⚠️ **GitHub.com Fallback** ⚠️