나뭇잎 교과서

알고리즘과 그 표현

문제를 푸는 절차를 누가 따라 해도 같은 결과가 나오도록 명확한 단계로 쓴 것이 알고리즘입니다. 알고리즘은 순서도나 의사코드로 나타냅니다. 이 절에서는 최대공약수를 구하는 유클리드 호제법을 순서도에서 한 단계씩 실행해 보고, 그럴듯해 보이는 알고리즘에서 경계 입력에 숨은 오류를 찾아봅니다.

먼저 알면 좋은 것 최대공약수점화 관계
본문

알고리즘과 그 표현

본문

1071과 462의 최대공약수를 구하려 한다. 두 수를 소인수분해하는 방법은 수가 커지면 매우 오래 걸린다. 그런데 "큰 수를 작은 수로 나눈 나머지를 구하고, 작은 수와 그 나머지로 같은 일을 되풀이하다가 나머지가 0이 되면 마지막 나누는 수가 답이다"라는 절차를 따르면 세 번의 나눗셈으로 21을 얻는다. 이 절차를 사람이 아닌 기계가 따르게 하려면 어떻게 적어야 할까? 그리고 절차가 늘 옳은 답을 낸다는 것은 어떻게 확인할까?

단계ab나눗셈나머지 110714621071 = 2 × 462 + 147147 2462147462 = 3 × 147 + 2121 314721147 = 7 × 21 + 00 나머지가 0이 될 때의 b = 21이 최대공약수이다.
그림 1. 매 단계 \((a, b)\)를 \((b, a\text{를 }b\text{로 나눈 나머지})\)로 바꾸며, 나머지가 0이 되면 멈춘다.
먼저 알아야 할 것최대공약수, 나눗셈의 몫과 나머지, 점화 관계(점화 관계와 일반항).

2.3.1기계가 따를 수 있는 절차

어떤 문제를 풀기 위해 정해진 순서대로 따르는 명확한 단계들을 알고리즘이라 한다. 알고리즘은 입력과 출력이 정해져 있고, 각 단계가 뜻이 하나로만 해석될 만큼 명확하며, 유한한 단계 안에 반드시 끝나야 한다. "적당히 나눈다"처럼 모호한 지시나, 끝나지 않을 수도 있는 되풀이는 알고리즘이 아니다. 알고리즘은 자연어로 단계를 번호 매겨 적거나, 상자와 화살표로 흐름을 그린 순서도로 나타내거나, 프로그래밍 언어와 비슷한 의사코드로 적는다.

정리 2.3.1 유클리드 호제법

자연수 \(a\), \(b\)에 대해 \(a\)를 \(b\)로 나눈 나머지를 \(r\)라 하면 \(\gcd(a, b) = \gcd(b, r)\)이다. 그래서 \((a, b) \to (b, r)\)을 나머지가 0이 될 때까지 되풀이하면, 마지막의 0이 아닌 나누는 수가 최대공약수이다. 나머지는 매번 줄어드는 음이 아닌 정수이므로 이 절차는 반드시 끝난다.

호제법이 옳은 까닭은 \(a = qb + r\)에서 \(a\)와 \(b\)의 공약수는 \(r = a - qb\)도 나누고, \(b\)와 \(r\)의 공약수는 \(a = qb + r\)도 나누기 때문이다. 그래서 두 쌍의 공약수는 모두 같고, 최대공약수도 같다. 반드시 끝나는 까닭은 나머지가 나누는 수보다 작아 매 단계 줄어드는 자연수이고, 자연수는 한없이 줄어들 수 없기 때문이다. 이렇게 알고리즘은 결과가 옳다는 것과 반드시 끝난다는 것을 함께 확인해야 한다.1

2.3.2어떤 입력에서 틀릴까

알고리즘을 만들 때는 예외적인 입력을 따져야 한다. 예를 들어 여러 수 가운데 가장 큰 수를 찾는 알고리즘을 "최댓값을 0으로 두고, 각 수가 최댓값보다 크면 최댓값을 그 수로 바꾼다"로 적으면, 입력이 모두 음수일 때 0이라는 틀린 답을 낸다. 최댓값의 처음 값을 첫째 수로 두어야 어떤 입력에서도 옳다. 이렇게 빈 입력, 음수, 같은 값이 여러 개인 경우처럼 경계에 있는 입력을 넣어 보는 것이 알고리즘의 오류를 찾는 좋은 방법이다.

처음 값입력 (−3, −7, −1)출력 최댓값 ← 0어느 수도 0보다 크지 않음0 (틀림) 최댓값 ← 첫째 수−3 → −3 → −1−1 (맞음) 확인할 입력모두 음수, 모두 같은 수, 수가 하나
그림 2. 보통의 입력에서는 둘 다 맞지만, 모두 음수인 입력에서 첫째 판본이 틀린다.

2.3.3예제 풀이

예제 2.3.2

유클리드 호제법을 단계별 자연어로 적고, 이 알고리즘으로 \(\gcd(1071, 462)\)와 \(\gcd(89, 55)\)를 구하여라. 두 경우의 나눗셈 횟수를 비교하여라.

알고리즘은 이렇게 적는다. 1단계에서 두 자연수 \(a\), \(b\)를 입력받는다. 2단계에서 \(b = 0\)이면 \(a\)를 출력하고 끝낸다. 3단계에서 \(a\)를 \(b\)로 나눈 나머지 \(r\)를 구한다. 4단계에서 \(a\)를 \(b\)로, \(b\)를 \(r\)로 바꾸고 2단계로 돌아간다. 그림 1처럼 \(\gcd(1071, 462)\)는 세 번의 나눗셈으로 21이다.

\(\gcd(89, 55)\)는 89, 55, 34, 21, 13, 8, 5, 3, 2, 1, 0으로 나머지가 이어져 아홉 번의 나눗셈 끝에 1을 얻는다. 89와 55는 이웃한 피보나치 수여서 몫이 매번 1이고 나머지가 가장 천천히 줄어든다. 그래서 두 수가 1000보다 작아도 나눗셈 횟수가 가장 많은 경우가 피보나치 수의 쌍이다. 즉 알고리즘은 입력, 명확한 단계, 출력, 그리고 반드시 끝남을 갖춘 절차이며, 옳은 결과와 끝남을 함께 확인하고 경계의 입력으로 오류를 찾아야 한다.

아래 탐구 카드에서 최대공약수를 구하는 절차를 순서도로 그려 보고, 알고리즘이 어떤 입력에서 틀리는지 찾아보자.

  1. 이 방법은 기원전 300년 무렵 유클리드의 『원론』에 실려 있어, 기록으로 남은 가장 오래된 알고리즘 가운데 하나로 꼽힌다. 알고리즘이라는 말은 9세기 페르시아의 수학자 알콰리즈미의 이름에서 왔다.
마무리

알고리즘은 입력과 출력이 정해지고, 각 단계가 명확하며, 유한한 단계 안에 끝나는 절차로, 자연어, 순서도, 의사코드로 나타낸다. 유클리드 호제법은 \(\gcd(a, b) = \gcd(b, r)\)을 되풀이하며 나머지가 줄어드는 자연수여서 반드시 끝나고, 알고리즘은 이렇게 옳음과 끝남을 함께 확인하며 경계의 입력으로 오류를 찾는다. 처음 질문의 1071과 462의 최대공약수는 세 번의 나눗셈으로 21이다.

탐구 1

최대공약수를 구하는 절차를 기계에게 어떻게 알려 줄까?

알고리즘과 순서도 · 조작

  1. 질문252와 198의 최대공약수를 소인수분해로 구할 수 있지만, 수가 수백 자리라면 소인수분해는 사실상 불가능하다. 누구나(기계도) 똑같이 따라 하면 반드시 답이 나오는 절차를 만들 수 있을까?
  2. 복원\(a = bq + r\)이면 \(a\)와 \(b\)의 공약수는 \(b\)와 \(r\)의 공약수와 같다. 그래서 \(\gcd(a, b) = \gcd(b, r)\)이다.
  3. 설명문제를 푸는 절차를 명확한 단계의 유한한 나열로 쓴 것을 알고리즘이라 한다. 알고리즘은 ① 입력과 출력이 분명하고, ② 각 단계가 모호하지 않으며, ③ 유한 번 만에 끝나야 한다. 유클리드 호제법은 "\(r \leftarrow a \bmod b\), \(a \leftarrow b\), \(b \leftarrow r\)"을 \(b = 0\)이 될 때까지 되풀이하고 \(a\)를 출력한다. 이 절차를 시작·끝(둥근 상자), 처리(직사각형), 판단(마름모)과 화살표로 그린 것이 순서도이고, 말로 적은 것이 의사코드이다.
  4. 연결나머지는 매번 줄어드는 자연수이므로 반드시 0에 도달한다. 이것이 "유한 번 만에 끝난다"의 증명이다. 인터넷 암호(RSA)의 열쇠를 만들 때 이 알고리즘이 쓰인다.
  5. 적용"한 단계"를 눌러 순서도의 어느 상자가 실행되는지, 표의 변수가 어떻게 바뀌는지 따라가자. 연속한 피보나치 수(예를 들어 89, 55)를 넣으면 반복 횟수가 어떻게 되는가?
  6. 판단순서에 주의해야 한다. \(a \leftarrow b\)를 먼저 하고 \(r \leftarrow a \bmod b\)를 하면 \(a\)가 이미 바뀌어 틀린 값이 나온다. 또 \(b = 0\)을 입력으로 넣으면 나눌 수 없으므로, 입력 조건을 알고리즘에 함께 적어야 한다.

확인조건이 달라지면

유클리드 호제법으로 \(\gcd(1071, 462)\)를 구할 때 나오는 나머지를 차례로 쓰면?

탐구 2

이 알고리즘은 어떤 입력에서 틀릴까?

알고리즘의 정확성 · 오류 찾기

처음 틀린 줄을 누르세요. 틀린 곳이 없으면 마지막 줄을 누르세요.

목표는 수 목록 x₁, …, xₙ에서 가장 큰 값을 출력한다.

  1. 입력은 수 목록 x₁, …, xₙ (n ≥ 1)입력 조건을 밝혔습니다.
  2. 최댓값 m ← 0여기가 틀렸습니다. 모든 수가 음수(예: −5, −2, −9)이면 0을 출력합니다. m ← x₁로 시작해야 합니다.
  3. i = 1부터 n까지 되풀이하며, xᵢ > m이면 m ← xᵢ비교와 갱신 자체는 맞습니다.
  4. m을 출력한다.앞의 초깃값 문제를 이어받습니다.
  5. 틀린 곳이 없다.음수만 있는 목록을 넣어 보세요.

  1. 질문알고리즘이 예시 몇 개에서 맞는 답을 냈다고 늘 맞을까? 어떤 입력을 넣어 봐야 숨은 오류가 드러날까?
  2. 복원알고리즘은 입력과 출력이 분명하고, 단계가 모호하지 않으며, 유한 번 만에 끝나야 한다.
  3. 설명자주 나오는 오류는 ① 초깃값(최댓값을 0으로 시작), ② 경계 오류(<와 ≤, 한 번 덜 또는 더 반복), ③ 끝나지 않는 반복(범위가 줄지 않음)이다. 이런 오류는 경계와 극단의 입력(n = 1, 빈 목록, 모두 음수, 원소 두 개)에서 잘 드러난다. 알고리즘이 맞다는 것은 몇 개의 예가 아니라 모든 입력에 대한 논증(반복할 때마다 지켜지는 성질, 매번 줄어드는 양)으로 보인다.
  4. 연결대수 과목에서 배운 수학적 귀납법과 같은 생각이다. "반복 전에 성립하고, 한 번 반복해도 유지되는 성질"을 찾으면 알고리즘이 끝났을 때의 결과가 맞음을 증명할 수 있다.
  5. 적용네 알고리즘에서 처음 틀린 줄을 찾자. 틀린 알고리즘마다 틀린 답을 내는 입력을 하나씩 직접 만들어 보자.
  6. 판단테스트를 많이 통과해도 오류가 없다는 보장은 없다. 반대로 반례 하나면 틀렸다는 것은 확실하다. 확인할 때는 평범한 입력보다 경계의 입력을 먼저 넣어 보자.

확인조건이 달라지면

"i = 1부터 n − 1까지 반복"하는 반복문은 몇 번 실행될까? (n ≥ 2)

AI 출제 문항연습문제 풀어보기11문항
Esc

↑↓ 고르기Enter 열기