일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- flask연동
- join
- 트럼프 대통령 트윗 분석하기
- linux
- update
- insert
- PYTHON
- KDT
- 리액트
- GitLab
- Effect Hook
- 백준 알고리즘
- 알고리즘
- Git
- K-Digital training
- 엘리스
- 리눅스
- sql
- pandas
- openapi
- mongodb
- merge request
- 자료구조
- State Hook
- 영어 단어 모음 분석하기
- delete
- 엘리스 AI 트랙
- 엘리스AI트랙
- 파이썬
- git remove
- Today
- Total
GO WILD, SPEAK LOUD, THINK HARD
엘리스 AI 트랙 11주차 - 알고리즘의 정석 I (3/12) 🔥 본문
✔ 11주차. 자료구조와 알고리즘
<학습 목표>
- 개발 역량 강화를 위한 자료구조 및 알고리즘 문제를 수행할 수 있습니다.
- 알고리즘 문제를 만났을 때 효율적으로 접근하는 방법을 알 수 있습니다.
- 알고리즘 문제 해결 기법의 근본적인 이해를 할 수 있습니다.
[01 재귀호출]
1. 알고리즘이란?
- 알고리즘 : algorithm. 문제를 해결하는 방법. 계산을 통하여 해결할 수 있는 문제를 해결하는 방법
- 알고리즘의 5가지 성질 : 유한성, 명확성, 입력, 출력, 효과성
- k번째 숫자 찾기
- 해결방법 : 숫자 입력 받기 → 지금까지 받은 숫자들을 정렬 → k번째로 작은 숫자를 출력
def findKth(myInput, k) :
result = []
data = []
for element in myInput:
data.append(element)
data.sort()
# data[k-1] = 찾는 값
if len(data) < k:
result.append(-1)
else:
result.append(data[k-1])
return result
2. 재귀호출
- 재귀호출 : 함수가 자기자신을 호출함.
- 팩토리얼의 재귀적 정의 : n! = n * (n-1)!
3. 수학적 귀납법
- 수학적 귀납법 : 모든 자연수가 어떤 주어진 성질을 만족시킨다는 명제를 증명하는 방법의 하나 (출처 : 위키백과)
# 여기서요? 갑자기요? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 진짜 나 왜 수포자냐...
- 명제 P(n)을 증명하는 방법
① N = 1 일 때 성립함을 보인다.
② P(k)가 성립한다고 가정할 때, P(k+1)이 성립함을 보인다.
③ 따라서 모든 자연수 n에 대하여 P(n)이 성립한다.
→ 수학적 귀납법이란 명제를 재귀적으로 증명하는 방법임.
→ 재귀적 증명법이란 하나의 명제를 증명하기 위해 그 증명에 똑같은 증명을 사용하는 것.
4. 퀵 정렬
- 퀵 정렬 : Quick Sort, 재귀호출을 이용한 대표적인 정렬.
- 퀵 정렬 구현하기
def quickSort(array):
# 기저조건 처리
# array의 길이가 0 또는 1이라면 이미 정렬된 것 -> 정렬필요X
if len(array) <= 1:
return array
# 기저조건이 아니라면
pivot = array[0]
# pivot보다 작거나 같으면 left
left = getSmall(array[1:], pivot)
# pivot보다 큰 값은 right
right = getLarge(array[1:], pivot)
return quickSort(left) + [pivot] + quickSort(right)
# array에서 pivot보다 작거나 같은 값을 리스트로 리턴
def getSmall(array, pivot):
data = []
for a in array:
if a <= pivot:
data.append(a)
return data
# array에서 pivot보다 큰 값을 리스트로 리턴
def getLarge(array, pivot):
data = []
for a in array:
if a > pivot:
data.append(a)
return data
5. 재귀함수 디자인
- 재귀함수를 디자인 하기 위한 세가지 단계
① 함수의 정의를 명확히 한다.
② 기저 조건 (Base condition)에서 함수가 제대로 동작하게 작성한다.
③ 함수가 작은 input에 대하여 제대로 동작한다고 가정하고 함수를 완성한다.
- 예) 올바른 괄호인지 판단하는 재귀함수 디자인
① isRightParenthesis(p) 함수를 정의 (p가 올바른 괄호이면 "YES", 아니면 "NO" 를 반환하는 함수)
② 기저조건 : 괄호가 없거나, 괄호가 하나만 있는 경우
③ 인접한 괄호쌍을 제거하는 방식으로 올바른 괄호인지 판단
def checkParen(p):
'''
괄호 문자열 p의 쌍이 맞으면 "YES", 아니면 "NO"를 반환
1. 기저조건 처리
2. p에서 인접한 괄호쌍을 찾아 제거
3. 제거된 문자열을 checkParen 함수에 다시 확인
'''
# 기저조건 처리
if len(p) == 0:
return "YES"
elif len(p) == 1:
return "NO"
# 기저조건이 아닐 경우
for i in range(len(p)-1):
if p[i] == "(" and p[i+1] == ")":
q = p[:i] + p[i+2:]
return checkParen(q)
return "NO"
[02 문제 해결 절차, 완전 탐색, 시간복잡도]
1. 문제 해결의 절차
- 문제 해결의 절차
① 문제를 정확히 이해한다
② 문제를 해결하는 알고리즘을 개발한다
③ 알고리즘이 문제를 해결한다는 것을 증명한다
④ 알고리즘이 제한시간내에 동작한다는 것을 보인다
⑤ 알고리즘을 코드로 작성한다
⑥ 제출 후 만점을 받고 매우 기뻐한다
2. 시간 복잡도
- 시간 복잡도 : 이 알고리즘이 몇 개의 명령을 수행하는지 → 프로그램의 수행 시간을 유추
- Big-O 표기 : 최악의 경우에 수행하는 명령 수
3. 완전 탐색
- 완전 탐색 : Brute-Force, 가능한 모든 경우를 시도해 보는 것 (가장 기초적이고 쉬운 방법).
📌문제가 주어지면 무조건 완전 탐색법으로 먼저 시도해봐야 함!
- 멱집합 구하기 (멱집합 : 어떤 집합의 모든 부분 집합을 모은 집합)
→ n부터 n까지의 자연수가 들어있는 집합의 멱집합을 구하는 예제
# 1부터 n까지 자연수의 원소가 있을 때, k를 가장 처음으로 선택하는 경우 반환
# ex) 3,2 -> [[2], [2, 3]] / 3, 3 -> [[3]]
def getPowerSet(n, k):
# 기저조건
if n == k :
return[[k]]
# 기저조건이 아닐때
result = [[k]]
# temp에 k 다음에 올 숫자를 담아야 함
temp = []
for i in range(k + 1, n + 1):
temp = temp + getPowerSet(n, i)
for i in range(len(temp)):
temp[i] = [k] + temp[i]
return result + temp
def powerSet(n) :
result = []
for i in range(1, n + 1):
result += getPowerSet(n, i)
return result
4. Complexity
- Complexity Theory : 복잡도 이론. 각 문제마다 풀이의 시간복잡도가 다름.
- P class : 다항 시간안에 풀 수 있는 문제.
- NP class : 답을 검증하는데 다항시간이 걸리는 문제.
- NP-Complete class : NP class 문제들 중에서도 푸는데 가장 오랜 시간이 걸리는 문제.
→ 알고리즘과정에서는 거의 대부분 P문제들을 다룬다
# 이.....거는 지금 내 레벨에서 알기 어려운 듯....
[03 분할정복법]
1. 재귀호출을 이용한 문제 해결
- 가장 가까운 값 찾기 : 정렬된 n개의 숫자 중 정수 m과 가장 가까운 값 찾기
→ 이진탐색 사용(O(logn))
# m과 가장 가까운 값 후보인 두 값을 리턴하는 함수
def getNearestInternal(data, m):
# 기저조건일 때
if len(data) == 1:
return(data[0], data[0])
elif len(data) == 2:
return(data[0], data[1])
# 기저조건 아닐 때
# 중앙값을 가르키는 mid
mid = len(data) // 2
# mid 보다 m 이하라면 mid보다 왼쪽에 있는 데이터는 버리고 재귀호출
if data[mid] <= m:
return getNearestInternal(data[mid:], m)
# mid 보다 m 이 크다면 mid보다 오른쪽에 있는 데이터는 버리고 재귀호출
else:
return getNearestInternal(data[:mid+1], m)
def getNearest(data, m) :
# 가장 가까운 값 후보인 두 값을 찾고, 더 가까운 값을 반환
val = getNearestInternal(data, m)
# value[0] : m 이하면서 가장 가까운 값 / value[1] : m 이상이면서 가장 가까운 값
if m - val[0] <= val[1] - m:
return val[0]
else:
return val[1]
- 거듭제곱 구하기
LIMIT_NUMBER = 1000000007
def getPower(m, n):
# 기저조건
if n == 0:
return 1
elif n % 2 == 0:
temp = getPower(m, n // 2)
return (temp * temp) % LIMIT_NUMBER
else:
temp = getPower(m, (n - 1) // 2)
return (temp * temp * m) % LIMIT_NUMBER
2. 분할정복법
- 분할정복법 : 문제를 소문제로 분할 → 각각의 소문제를 해결 → 소문제의 해결 결과를 이용해 전체문제를 해결
- 작은 문제로 분할하는 것이 어려움, 수학적 문제 해결 능력이 가장 중요, 키보드 대신 노트와 펜을 들고 생각
# 내가 풀 수 있는 문제가 아닌 것...같.....다.........
- 합병정렬 구현 : 리스트를 분할 해 정렬 → 분할한 리스트를 다시 비교해 정렬. 시간복잡도 O(nlogn)
import math
def mergeSort(data) :
# 기저조건
if len(data) == 1:
return data
mid = len(data) // 2
left = mergeSort(data[:mid])
right = mergeSort(data[mid:])
result = []
leftPtr = 0
rightPtr = 0
while leftPtr < len(left) or rightPtr < len(right):
leftVal = left[leftPtr] if leftPtr < len(left) else math.inf
rightVal = right[rightPtr] if rightPtr < len(right) else math.inf
if leftVal < rightVal:
result.append(leftVal)
leftPtr += 1
else:
result.append(rightVal)
rightPtr += 1
return result
- 연속부분 최대합
def getSubsum(data) :
n = len(data)
if n == 1 :
return data[0]
# 왼쪽/오른쪽/양쪽 연속부분 최대합 구하기
mid = n // 2
left = getSubsum(data[:mid])
right = getSubsum(data[mid:])
Sum = 0
leftSum = 0
rightSum = 0
for i in range(mid - 1, -1, -1):
Sum += data[i]
leftSum = max(Sum, leftSum)
Sum = 0
for i in range(mid, n):
Sum += data[i]
rightSum = max(Sum, rightSum)
return max([left, right, leftSum + rightSum])
[04 탐욕적 기법]
1. 탐욕적 기법
- 탐욕적 기법 : 순간의 최적의 선택이 궁극적으로 최적의 선택이다, 단순한 방법도 문제 풀이에는 충분하다 ☞더 알아보기
- 거스름돈 # 이거 분명히 수업 들었던 건데 왤케 생소하냐...
def coinChange(n) :
# 동전갯수 저장
result = 0
# 동전 각각의 액수를 리스트에 저장
coins = [100, 50, 10, 5, 1]
for coin in coins:
result += n // coin
# 지불하고 남은 액수
n = n % coin
return result
- 기울기가 가장 큰 두 점 찾기
def getSlope(a, b):
return abs((b[1] - a[1]) / (b[0] - a[0]))
def maxSlope(points) :
# 포인트를 x 축 기준으로 정렬
points.sort()
# 최대 기울기 저장
result = 0
# 포인트를 돌면서 x 축으로 인접한 값을 비교
for i in range(len(points) - 1):
result = max(result, getSlope(points[i], points[i+1]))
return result
- Fractional knapsack
def fKnapsack(materials, m) :
# 물건을 가성비 순서대로 넣어야함 (무게낮고 가치높은)
# x[0] : 무게 / x[1] : 가치
materials = sorted(materials, key = lambda x : x[1] / x[0], reverse = True)
weight = 0
value = 0
for i in range(len(materials)):
# 물건을 넣어도 버틸 수 있는 무게까지 남아 있을 때
if weight + materials[i][0] < m:
weight += materials[i][0]
value += materials[i][1]
# m 만큼 무게가 꽉 찼을 때
elif weight + materials[i][0] == m:
weight += materials[i][0]
value += materials[i][1]
return value
# 물건을 넣으면 무게를 넘어갈 때
else:
temp = m - weight
value += temp * (materials[i][1] / materials[i][0])
return value
return value
※ 수업 자료의 출처는 K-Digital Training x 엘리스 인공지능 서비스 개발 기획 1기 (elice.io/)입니다.
'개발 > 엘리스 AI 트랙' 카테고리의 다른 글
엘리스 AI 트랙 12주차 - 파이썬으로 시작하는 데이터 분석 I (3/17)🔥 (0) | 2021.03.26 |
---|---|
엘리스 AI 트랙 11주차 - 알고리즘의 정석 II (3/14) 🔥 (0) | 2021.03.25 |
엘리스 AI 트랙 11주차 - 알고리즘을 위한 자료구조 (3/10) 🔥 (0) | 2021.03.14 |
엘리스 AI 트랙 08주차 - OpenAPI 심화 (2/20)🔥 (0) | 2021.02.21 |
엘리스 AI 트랙 08주차 - OpenAPI 기초 (2/19)🔥 (0) | 2021.02.21 |