본문 바로가기

Upstage AI Lab 2기

Upstage AI Lab 2기 [Day040] 자료구조 및 알고리즘 (1)

Upstage AI Lab 2기

2024년 2월 6일 (화) Day_040

 

Day_040 실시간 강의 : 자료구조 및 알고리즘 (1)

(노정호 강사님

https://www.nossi.dev/

https://www.youtube.com/@nossi-dev)

 


1일차 수업 요약 키워드

메모리 구조 - LIST : Array, LinkedList
시간복잡도
완전 탐색 - 조합 / 순열 / 부분집합 - 재귀함수


 

3일 강의의 목표

① 코딩테스트 합격

② 자료구조 & 알고리즘을 알아두면 구현에 도움이 됨

 

우선순위

1. 완전탐색, 그래프 + BFS + DFS (절반 이상의 비중을 차지)

+ α : 문자열, backtracking, dynamic programming, hashtable

 

코딩테스트 팁

https://www.nossi.dev/cote/tip

기본적인 문제는 풀 수 있는 것을 목표로!

but 각자의 원칙과 목표에 맞춰서 우선순위 조절

 

문제해결능력

구현능력

 

언어는 python 추천

 

면접과 코딩테스트는 다른 전략이 필요

 

이론 + 문제풀이 병행

주력언어를 정하고 체화

스터디를 꼭 하자!

 

'할 때 제대로 하자' <- 강사님의 강조

 


 

자료구조(Data Structure)

: 데이터를 저장하고 관리하는 방식

자료구조를 배워야 하는 이유 : 자료구조와 알고리즘은 긴밀한 관계가 있다.

 

알고리즘(Algorithm)

: 문제해결 방법

자주 쓰이는 문제는 해결방법이 패턴화(≒ 일반화)되어 있다.

일반화 -> 주어진 문제에 맞게 변형하고 응용해야 함

 

어느 정도 이해를 기반으로 암기가 필요

 

BFS, DFS, Binary Search, Dijkstra는 능수능란하게 쓸 수 있어야 함.

 


알고리즘 평가 기준

- 시간복잡도

- 공간복잡도

 

https://www.nossi.dev/cote/timecomplexity

https://www.youtube.com/watch?v=GHClo7yu_20&t=13s


메모리 구조

- 시간복잡도와 관련

어디에 저장하는가

HDD(하드디스크) / RAM

 

파일 자체는 하드디스크에 저장

실행할 때는 RAM 메모리에 잠시 올려놓는다.

∴ 동시에 많이 실행 -> RAM 많이 필요

즉, 우리에게는 RAM이 중요

 

 


LIST

순차적 자료구조 (ADT, Abstract Data Type, 추상자료형)

- Array : 메모리 상 인접 (continuous) (e.g. python list)

- LinkedList : 메모리 상 인접 X (discontinuous), link(pointer)가 꼭 필요

 

∴ 메모리구조상 어떻게 저장되는지 알아야 함

 

자료구조 -> 구현방식은 다양

list / heap / queue / stack -> array, linkedlist 등 구현방식 다양

이 자료구조가 무엇을 하고 싶은지 생각하기!

 

메모리구조 특성

- Array

: 맨 첫번째 자료의 주소가 저장되어 있고, 간격이 일정함(예: 4byte 또는 8byte)

산술연산 한번으로 접근 가능 - 즉시 실행

시간복잡도 O(1)

 

- LinkedList

: node를 다 타고 들어가야 함. (예. 100번째 자료 접근 -> 100번 실행)

n ↑ 시간 ↑ 

시간복잡도 O(n)

대신 node 삭제할 때 빠름

 

즉, 같은 list 도 array로 구현 하면 O(1), linkedlist로 구현하면 O(n)

각자 더 적합한 상황이 있음


bit 의 어원 binary digit

binary 0b

↓ 4bit = 1자리

hexadecimal 0x

 

RAM 설명

메모리 할당

C언어는 어떻게 구성되어 있는가

 

integer -> 4 byte(32bit)로 표현

1bit : 부호

31bit : 숫자 표현

-2^31 ~ 2^31

 

LinkedList (discontinuous)

value+ address(of next node)

(4byte or 8byte) + (4byte or 8byte) -> 8byte  each

4byte or 8byte -> 시스템/컴퓨터마다 다를 수 있음

 

파이썬의 경우 dynamic array -> element의 자료형이 다 달라도 알아서 처리해줌

 


Running Time 실행 시간에 대한 이해가 중요함

-> 확장성을 고려

 

실행할 때마다 개별 running time은 다름 (캐시메모리 상황, cpu 성등 등등)

-> 매번 실행 시간을 계산할 수는 없음

-> 경향성으로 판별

--> Big O

 

Big O

10^8 -> 1초~10초 정도

넘지 말아야 할 선을 판별할 수있음

 

대세에 지장을 주는 부분 -> 반복문

 

input(n) -> n이 미지수이니까 n을 기준으로 생각할 수 있어야 함

 

예) 5n^2 + 3n +30

최고차항 외에는 의미 없어짐

 

Big-O notation

 

https://en.wikipedia.org/wiki/Big_O_notation

n! >> n^5 > n^4 > n^3 > n^2 > nlogn > n > logn

 

예)

5n^2 + 3n + 30 -> O(n^2)

5n + 30 -> O(n)

5030 -> O(1)

 

best case / average case / worst case

같은 알고리즘, 같은 환경, 같은 사이즈의 input 이어도 input 형태에 실행시간이 달라질 수 있음

예를 들어 insertion sort의 경우 worst case 는 O(n^2), best case 는 O(n)

average case 자체는 계산하기 어렵지만

average case ∽ worst case (-> upper bound)

보수적으로 마지노선인 worst case를 기준으로 정함

 

예)

문제에는 input들의 범위를 정해줌

nums.length <= 10^5 : O(n^2)인 알고리즘을 사용하면 안된다는 의도

-10^9 <= nums[i] <= 10^9 : int 자료형 써도 된다는 의도 (c, java 등 type이 strict한 언어들)

 

 


note. 우리는 우선순위를 정해서 공부할 것임

 

1.0 알고리즘 패러다임 (Algorithm paradigm)

① 완전 탐색 - BFS, DFS, 재귀, 반복문 etc

② Backtracking

③ Greedy

④ Divide and Conquer

⑤ Dynamic Programming

 

패러다임 인지하면 사용할 알고리즘을 선택하는데 도움이 됨.

 


완전 탐색

(모든 탐색의 근본)

모든 가능성을 탐색함 -> 시간 복잡도 매우 높아질 수 있음

(but '컴퓨터'를 사용하는 근본적인 목적은 완전 탐색을 하기 위함임)

 

완전탐색을 사용하는 경우

1. 범위가 작을 때

2. 일단은 해볼 때

(시간초과하더라도 일단 만들고 나면 최적화할 수 있는 부분이 보임)

 

완전탐색이 사용되는 예시

1. 모든 가능성 탐색

2. 부분집합생성

3. 순열과 조합

4. 격자탐색

 

코드 구현 : 반복문, 재귀, 비트마스크

 

코딩테스트에 꼭 나오는 편

더보기

예제 1)

1. Two Sum

https://leetcode.com/problems/two-sum/description/

 

내 풀이

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(1, len(nums)-i) :
                if nums[i] + nums[i+j] == target:
                    return [i, i+j]

 

 

대희님 코드

def twosum(nums=[4, 9, 7, 5, 1, 10, 15, 56], k=3, target=17):
    length = len(nums)

    for i in range(length):
        value = nums[i]

        if k == 1:
            if value == target:
                return [i]
        else:
            next_target = target - value
            result = twosum(nums[i + 1 :], k - 1, next_target)

            if result is None:
                continue
            else:
                next_result = list(map(lambda x: x + i + 1, result))
                next_result.append(i)

                return next_result

 

강사님 코드

- 재귀로 구현

(+) 중첩함수

twosum() 함수 안에 recur()함수를 정의

(함수 나중에 조금 다듬기)

def twosum(nums, k, target):
    n = len(nums)
    start = 0
    answer = []
    
    def recur(start):
        # base case
        if len(answer) == k: # k 만큼 호출됐나?
            # if sum([nums[answer[i]] for i in range(k)]) == target:
            #     print(answer)
            for i in range(k):
                sum += nums[answer[i]]
            if sum == target:
                print(answer)
            #     sum += nums[i]
            # if nums[answer[0]] + nums[answer[1]] + nums[answer[2]] == target:
                # print(answer)
            return False
        
        #recursion
        for i in range(start, n):
            answer.append(i)
            recur(i+1)
            answer.pop()
            # append, 재귀, pop 이런 구조!!!!!!!!!!
            
    return recur(start)

 

받은 피드백 정리하기

# 중첩함수

# 대희님 : 인자로 받은 list의 요소들의 합을 반환하는 sum 함수 같은 걸 만들어서 사용해야 할 것 같아요
# 주형님 : sum(list)하면 되니까요 근데 이건 k==3일 때 한정인 코드라 k==n일 때를 가정하면, target == n_sum이면 nums[i]를 뺀 거에서 new_target = n-1_sum이런 식으로 재귀 불러도 될 것 같아요
# 현지님 : 
#  if len(answer) == k:
#       sum = 0
#       for idx in answer: 
#         sum += nums[idx]
#       if sum == target:         print(answer, np.take(nums, answer)) 
# 저는 그냥 여기는 반복문으로 구현했어요! 여기서 np.take써서 해당 인덱스에 맞는 원소를 뽑아내서 같이 볼 수 있도록 해봤어욥!

 

학습자료 2.4 재귀 (Recursion) 보면서 복습하기!

 

 

 

예제 2) 조합 / 순열 / 부분집합

(1) combination

(1-1) given : list, k

더보기
def combination(nums, k):
    n = len(nums)
    ans = []
    def recur(start, tmp_ans):
        # base case        
        if len(tmp_ans) == k:
            ans.append(tmp_ans[:])
            return
    
        # recursion
        for i in range(start, n):
            tmp_ans.append(nums[i])
            recur(i+1, tmp_ans)
            tmp_ans.pop()
    
    recur(0, [])
    
    print(ans)

 

 

(1-2) given : n, k

더보기
def combination_n(n, k):
    ans = []
    tmp_ans=[]
    def recur(start):
        if len(tmp_ans) == k:
            ans.append(tmp_ans[:])
            return
        
        for i in range(start, n+1):
            tmp_ans.append(i)
            recur(i+1)
            tmp_ans.pop()
    recur(1)
    print(ans)

 

(2) permutation

(2-1) given : list, k

더보기
def permutation(nums, k):
    n = len(nums)
    ans = []
    tmp_ans=[]
    def recur(start):
        # base case        
        if len(tmp_ans) == k:
            ans.append(tmp_ans[:])
            return
    
        # recursion
        for i in range(n):
            if nums[i] not in tmp_ans:
                tmp_ans.append(nums[i])
                recur(i+1)
                tmp_ans.pop()
    
    recur(0)
    
    print(ans)
    return ans

 

(2-2) given : n, k

더보기
def permutation_n(n, k):    
    ans = []
    def recur(start, tmp_ans):
        # base case        
        if len(tmp_ans) == k:
            ans.append(tmp_ans[:])
            return
    
        # recursion
        for i in range(1, n+1):
            if i not in tmp_ans:
                tmp_ans.append(i)
                recur(i+1, tmp_ans)
                tmp_ans.pop()
    
    recur(0, [])
    
    print(ans)
    return ans

 

 

(3) subset

(복습 후 코드 정리해서 넣기)

 

 

 

더보기

참고자료

itertools에서 combinations를 import 해서 한줄로 구현 가능 

https://leetcode.com/problems/combinations/solutions/3847596/python-code-only-1-line-beats-99-89/?source=submission-ac

from itertools import combinations
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        return list(combinations(range(1, n+1), k))

 

 

 


 

재귀함수의 구조

1. 점화식

ans.append(i)
recur(i+1)
ans.pop()

2. base case

(무한반복되지 않도록 무조건 탈출 케이스가 있어야 함)

 

https://www.nossi.dev/edd45041-6c62-4d69-badc-b14af14def31


Backtracking

완전탐색을 최적화한 패러다임

solution이 될 가능성이 없을 때 반복문을 빠져나옴

if 문 한줄만 추가하면 됨


숙제:

https://leetcode.com/problems/word-search/description/

 

 


기타 참고 자료

 

https://www.inflearn.com/course/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%85%EB%AC%B8-%ED%8C%8C%EC%9D%B4%EC%8D%AC?inst=6603e386&utm_source=instructor&utm_medium=referral&utm_campaign=inflearn_%ED%8A%B8%EB%9E%98%ED%94%BD_promotion-link

 

강의자료 keynote로 만드신 듯 -> keynote 알아보기

 

https://pythontutor.com/