컴퓨터공학을 전공하고 있는 학생이라면, 지금쯤 알고리즘이란 무엇인가라고 정의 내릴 수 있을 것이다.
교수님에 따라, 전공책에 따라 약간의 차이는 있겠지만, 일반적으로 우리는 어떤 문제를 해결하기 위한 명확한 절차(과정)나 규칙, 명령어들의 집합을 알고리즘이라고 일컫는다.
그리고 모든 알고리즘들은 5가지의 규칙을 반드시 따라야한다!
알고리즘은 하나 이상의 입력값을 가져야 한다.
입력값은 0개 이상일 수 있으며, 적절한 범위를 가져야 한다.
예를 들어, 정렬 알고리즘이라면 정렬할 리스트가 입력값이 됨
알고리즘은 최소한 하나 이상의 출력을 반환해야 한다.
출력값은 유효한 결과여야 하며, 알고리즘이 수행된 후 의미 있는 값을 반환해야 함.
단순히 print()만 하는 것이 아니라, 논리적인 연산 결과가 나와야 함.
알고리즘의 모든 단계는 명확해야 한다.
각 단계는 애매모호함이 없어야 하고, 특정한 작업을 수행해야 한다.
같은 입력을 주었을 때 항상 같은 동작을 해야 한다.
알고리즘은 반드시 유한한 단계를 거쳐 종료되어야 한다.
무한 루프에 빠지면 안 되며, 일정한 시간이 지나면 결과를 출력해야 함.
각 단계는 충분히 단순하고, 기계적으로 수행할 수 있어야 한다.
사람이 이해하기 어려운 절차가 아니라, 명확한 연산으로 표현되어야 함.
즉, 실제로 실행 가능한 명령어들로 구성되어야 한다.
알고리즘은 데이터를 초기화하고, 수정하고, 삭제하는 등의 작업을 수행한다.
즉, 대부분의 알고리즘은 어떤 형태로든 데이터를 처리하는 과정을 거친다는 것이다.
당신은 시간 빌게이츠인가?, 알고리즘이 모든 경우에 대해 올바르게 동작하는지 일일이 검증하는 것은 불가능하다!!
따라서, 수학적 증명 방법을 사용해야 한다. (예: 귀납법, 불변식 사용)
알고리즘을 검증하는 핵심적인 방법 중 하나는 불변식(Invariant)을 찾는 것!
불변식이란 알고리즘이 실행되는 동안 변하지 않는 성질을 의미해.
예를 들어, 정렬 알고리즘에서 "이미 정렬된 부분은 항상 정렬 상태를 유지한다"라는 것이 불변식이 될 수 있음.
알고리즘을 모든 경우에 대해 검증하는 것은 비효율적이므로, 변하지 않는 속성(불변식)을 찾는 것이 중요하다는것이 핵심이다.
또한 이러한 불변식은 알고리즘이 올바르게 동작하는지 검증하는 데 사용될 수 있다.
반복문이 처음 실행되기 전에 불변식이 참임을 보여야 함.
만약 반복문이 한 번 실행되기 전 불변식이 참이라면, 실행 후에도 참이어야 함.
반복문이 종료될 때 불변식이 알고리즘의 정당성을 보장해야 함.
🔥 즉, 알고리즘을 분석할 때 "뭐가 변하는가?"보다는
"뭐가 변하지 않는가?"를 찾는 것이 중요한 전략이라는 것이다! 🔥
그럼 말 나온김에 sorting 알고리즘에 대해 알아보도록 하자.
sorting, 즉 정렬은 가장 기본적인 알고리즘이다.
n개의 숫자가 랜덤하게 나열되어있을때, 이를 순서대로(오름차순, 내림차순)등으로 재배열한는 것을 의미한다.
Sorting의 종류는 크게 Insertion(삽입) Sort, Shell sort, Quick Sort, Merge(병합) Sort, Heap Sort 등이 있다.
가장 먼저 제일 난이도가 낮은 Insertion Sort부터 알아보자!
삽입 정렬(Insertion Sort)은 데이터를 하나씩 확인하면서 적절한 위치에 삽입하는 방식의 정렬 알고리즘이다. 이미 정렬된 부분과 그렇지 않은 부분을 나누고, 정렬되지 않은 데이터를 하나씩 가져와 적절한 위치에 삽입하는 방식으로 동작하는 특징을 가진다.
이는 마치 "카드 게임에서 손에 카드를 하나씩 정리하는 방식"과 같다!
Insertion-SORT(A)
for j=2 to A.length
key = A[j]
i = j -1
while i > 0 and A[i] > key
A[i+1] = A[i]
i = i - 1
A[i+1] = key
삽입정렬을 의사코드로 나타내면 위와 같다.
이 코드를 통해서 우리는 삽입 정렬의 동작 원리를 요약할 수 있다!
1️⃣ 처음엔 4 혼자 있어서 정렬된 상태
→ 4, 3, 1, 5, 2
2️⃣ 3을 뽑아 4보다 앞에 둔다
→ 3, 4, 1, 5, 2
3️⃣ 1을 뽑아 3보다 앞에 둔다
→ 1, 3, 4, 5, 2
4️⃣ 5는 이미 올바른 위치에 있으므로 그대로 둔다
→ 1, 3, 4, 5, 2
5️⃣ 2를 뽑아 1과 3 사이에 둔다
→ 1,2,3,4,5
위에서 학습했던 정확성 증명의 3가지 단계를 거쳐 삽입 정렬이 항상 올바르게 동작함을 수학적으로 증명해보자.
증명에 앞서, 정확성 증명에서 제일 강조했던 Loop Invariant를 정의해볼 것이다.
"반복문의 j 번째 반복이 시작될 때, 배열 A[1..j-1]은 항상 정렬된 상태이다."
1️⃣ 초기 조건 (Initialization)
j = 2일 때, A[1..j-1]는 단 하나의 원소 A[1]만 포함하고 있음.
하나의 원소는 자동으로 정렬된 상태이므로, 초기 조건이 만족됨.
2️⃣ 유지 조건 (Maintenance)
반복문의 각 단계에서 새로운 원소 A[j]를 삽입하기 위해, 기존 원소 A[j-1], A[j-2], ...를 하나씩 오른쪽으로 이동시켜 올바른 위치를 찾는다.
이렇게 하면 A[1..j]는 항상 정렬된 상태를 유지하며, j를 1 증가시키면, 새로운 A[1..j-1]이 정렬된 상태가 되어 불변식이 유지됨.
3️⃣ 종료 조건 (Termination)
마지막 반복(j = n)이 끝나면, A[1..n]이 정렬된 상태가 됨.
위 과정을 토대로 우리는 삽입 정렬이 항상 올바르게 동작함을 증명할 수 있다!
무튼, 이제 이 삽입정렬이 잘 작동한다는 것은 지겨울 정도로 증명했다.
그럼 그 다음으로 알고리즘에서 중요한 요소인 시간복잡도로 넘어가보자.