문제 해결을 위한 명확한 명령문의 연속을 통칭하는 말이다. 구체적으로, 알고리즘은 유효한 입력으로부터 유한한 시간 내에 요구되는 출력을 만들어 내야 한다.
두 수의 최대공약수를 찾는 방법에는 '유클리드 알고리즘'을 사용할 수 있다.
두 자연수 m,n(m > n)의 최대 공약수는 n과 m mod n의 최대 공약수와 같다.(이에 대한 증명은 귀류법을 사용한다.)
따라서 다음과 같은 절차를 거치면 최대 공약수를 구할 수 있다.
예시: gcd(60,24) = gcd(24,12) = gcd(12,0) = 12
def gcd(m,n):
if m<n: # swap
t=m
m=n
n=t
while n==0:
r= m % n
m=n
n=r
return m
다음과 같은 풀이는 알고리즘이 아니다.
이것이 적절하지 못한 이유는 입력값이 0 일 때도 올바른 출력을 만들어야하지만 입력값이 0 인 경우에 틀린 출력을 만들기 때문이다.
이 방식은 '소인수 분해를'어떻게 해야하는지 명확하게 기술되지 않았고, 3번도 명확하게 기술되지 않아 알고리즘이라고 할 수 없다.
이 과정은 매우 중요하다. 완벽하게 문제를 이해해야, 불필요한 재작업을 줄일 수 있다.
흔히 발생하는 문제에 대한 알고리즘은 이미 주어진 경우가 많지만 그렇지 않은 경우 직접 설계를 해야한다. 이때 정확한 알고리즘은 모든 유효한 입력에 대해 정확히 작동해야함을 명심하라.
실제로 알고리즘을 설계하는 것은 다양한 이유(순전히 문제와 알고리즘 설계가 맞지 않거나, 여러 설계 기법을 섞어 써야할 수 있다)로 어려울 수 있다.
이때 알고리즘의 연산에 적절한 자료구조를 선택하는 것이 매우 중요하다. 알고리즘과 자료구조를 합치면 그것이 바로 프로그램이다.
알고리즘을 설계할 때는 자연어, 수도코드(psudocode), 플로우 차트를 이용하여 설계할 수 있다. 주로 수도코드를 이용해 설계한다.
알고리즘이 실제로 모든 유효한 입력에 대해 유한한 시간내에 올바른 결과를 만들어내는지 증명한다.
대부분의 알고리즘들은 실제로 컴퓨터에서 프로그램으로서 구현되기위해 만들어졌다. 우리의 알고리즘도 마찬가지므로 실제 프로그램으로 변환될 때 잘못되거나 비효율적으로 변환되는 것을 피해야한다.
따라서 구현 하면서 테스팅을 하는것이 중요하다.
시간복잡도는 알고리즘이 입력값의 크기에 따른 동작 시간이 얼마나 걸리는지 나타낸다.
공간복잡도는 입력값의 크기에 따라 얼마나 많은 메모리 공간을 사용해야하는지 나타낸다.
공간복잡도는 현대에 들어서 대부분의 경우 신경쓰지 않아도 된다. 대부분의 경우 우리는 시간복잡도를 고려해야한다.
실행시간을 실제 시간으로 측정하면 실행환경마다 다를 수 있으므로 적절하지 못하다. 따라서 입력값에 따른 basic operation이 실행된 횟수로 시간복잡도를 표현해 실행시간을 표현한다.
basic operation은 알고리즘의 가장 안쪽 loop에서 가장 시간이 많이 소모되는 연산으로 선정된다. 예를들어 정렬 알고리즘에선 키 비교, 수학적 문제에서는 사칙연산등이 해당된다.
일부 알고리즘은 입력 갯수이외에 입력의 상태(정렬 여부 등)에 실행시가이 영향받을 수 있다. 이를 고려하기 위해 우리는 Worst-Case, Best-case, Average-case를 고려한다.
예를 들어 요소가 n개인 list에서 어떤 값을 찾기 위해 순차적으로 조회한다.
worst case: 찾는 값이 가장 뒤에 있다면, Cworst(n)=n이다. 다른 어떤 입력 상태에서도 이 크기를 넘을 수 없다.
best case: 찾는 값이 가장 앞에 있다면, Cbest(n)=1이다. 다른 어떤 입력 상태에서도 이 보다 작을 수 없다. 보통 최악의 경우보다 중요하지 않다.
average case: 찾는 값이 평범하게 중간 어딘가에 임의로 위치하고 있는 경우를 말한다. 위 두 케이스에 비해 정확히 측정하기 어렵다. 주어진 예시에서는 사진과 같이 측정된다.
시간복잡도를 비교할때에는 차수가 중요하며 상수, 계수는 무시된다. 이러한 접근을 점근 표기법이라고 한다.
세가지 표기법이 존재한다.
1. big-O: 𝚶(g(n))
g(n)보다 order of growth가 작거나 같은 함수들의 집합을 의미한다.
즉 어떤 알고리즘의 시간복잡도가 O(n^2) 라면 이 알고리즘은 {2*n^2, 0.5*n^2, 100*n,...} 중 하나이며 최악의 경우 order of growth가 n^2보다 작거나 같다는 의미이다.
2.big-Omega: 𝛀(g(n))
g(n)보다 order of growth가 크거나 같은 함수들의 집합을 의미한다.
즉 어떤 알고리즘의 시간복잡도가 Ω(n^2) 라면 이 알고리즘은 {2*n^2, 0.5*n^2, 100*n^3,...} 중 하나이며 최선의 경우 order of growth가 n^2보다 크거나 같다는 의미이다.
3. 𝚯(g(n))
g(n)과 order of growth가 같은 함수들의 집합을 의미한다.
즉 어떤 알고리즘의 시간복잡도가 𝚯(n^2) 라면 이 알고리즘은 {2*n^2, 0.5*n^2, 100*n^2,...} 중 하나이며 항상 order of growth가 n^2라는 의미다.
극한(lim)을 이용한다.
점화식을 이용하여 계산한다.
1. Inital condition(재귀가 멈추는 조건에 해당하는 지점)에서 basic operation이 몇번 실행되는지 확인한다. M(0)=x
2. 재귀 호출 전후로 몇번의 basic operation이 발생하는지, 몇번의 재귀호출을 하는지 확인한다. M(k+1)=i*M(k)+y
3. 후진 대입법(backward-substitution)을 이용해 간단하게 n을 매개변수로 가지는 함수형태로 표현한다.
basic operation: 디스크 옮기기
H(n): n개 디스크로 이루어진 하노이 탑을 완전히 옮길 때 디스크를 옮긴 횟수
초기식: H(1)=1
점화식: H(n+1)=H(n)+1+H(n)=2*H(n)+1
후진대입: H(n)=2*H(n-1)+1=4*(H(n-2))+3=...=2^n-1 * H(1) + 2^n-1 -1 = 2^n-1
시간복잡도: O(2^n)