고등학교 시절에도 배웠지만, 수학적 귀납법은 알고리즘의 기초이자 명확히 지켜야 할 규칙임.
수학적 귀납법의 기본형 : P(1)이 참이고, P(n-1) -> P(n)이 참이면 P(n)은 모든 자연수 n에 대해서 참이다.
여기서 P(1)은 Base, P(n-1) -> P(n)은 Step이라 함
수학적 귀납법의 기본형 : P(1)이 참이고, P(1), P(2), ... P(n-1) -> P(n)이 참이면 P(n)은 모든 자연수 n에 대해서 참이다.
좀 더 엄격한 형태. 대부분 기본형을 쓰므로 알고 있기만 하자
P(n-1) -> P(n)에서 P(n-1)이 true인지, false인지는 관계가 없음.
명제 P->Q의 의미
| P | Q | truth |
|---|---|---|
| true | true | true |
| true | false | false |
| false | true | true |
| false | false | true |
P가 참이든, 거짓이든, 상관이 없다! P가 거짓일때를 공허참(vacuous truth)이라고 함.
P : 7시에 일어난다, Q : 학교에 간다
여기서 명제 P -> Q는 하나의 약속이다. P라는 조건이 발생했을 때, Q를 지켰는가? 다시 말해 약속을 지켰는가?
n이 자연수이고, n>3 일때, n^2>10인 것은 모두 동의 할 것임. 이 때 n = 1인 것은 고려할 필요가 없으며, 공허참이라고 하는 것
"P(1)이 참이고, P(n-1) -> P(n)이 참이면 P(n)은 모든 자연수 n에 대해서 참이다." 에서 P(n-1)이 참일때만 P(n)이 참인지를 확인하면 된다. P(n-1)이 거짓이면 말 할 필요가 없기 때문
일반적으로 1부터 x까지 더하는 코드
unsigned int sum(unsigned int x)
{
int i, S;
S = 0;
for(i=0; i<=x; i++){
S += i;
}
return S;
}
재귀를 이용한 1부터 x까지 더하는 코드
unsigned int sum(unsigned int x)
{
if(x <= 0) return 0;
return x + sum(x-1);
}
sum(3)을 하면 어떻게 될까? sum(3) -> sum(2) -> sum(1) -> sum(0) -> 0 -> 1 -> 2 -> 3... 이렇게 될 것이다. 이것은 따라 들어가는 방법인데, 재귀에서는 별로 효율적이지 않다. 만약 sum(1000)을 하면? 그렇기에 우리는 재귀에서 증명의 방법을 통해 푸는것이 더 좋다.
그러므로 sum(x)는 1+2+...+x를 리턴한다.
unsigned int sum(unsigned int x)
{
if(x <= 0) return 0;
return x + sum(x-1);
}
재귀식을 복잡도로 표현하려면 식이 필요하다.
r(n) = 1 + r(n-1)
= 1 + 1 + r(n-2) = ... = n
∴r(n) = O(n)
일반적인 이진탐색
int search(int a[], int n, int x){
int l, r, m;
l = 0;
r = n-1;ㅣ
while(l <= r){
m = (l+r)/2;
if(a[m] == x) return m;
else if(a[m] > x) r = m-1;
else l = m+1;
}
return -1;
}
재귀적인 이진탐색
int search(int a[], int n, int x){
int l, r, m;
if(n <= 0) return -1;
m = n/2;
if(a[m] == x) return m;
else if(a[m] > x) search(a, m, x);
else {
r = search(a+m+1, n-m-1, x)
if (r == x) m;
else return =1;
}
}
Invariant(불변) : 만약 어떤 i에 대해서 a[i] == x라면, l <= i <= r이 항상 성립한다.
수학적 귀납법 : 만약 어떤 i에 대해 a[i] == x 면 i를 리턴한다.
시간복잡도
r(n) = 1 + r(n/2)
= 1 + 1 + r(n/4) = ... = log n
∴r(n) = O(log n)
일반적인 선택정렬
int sort(int a[], int n)
{
int i, j, m, t;
for (i = 0; i < n; i++){
m=i
for(j = i; j < n; j++){
if(a[m] > a[j]) m==j;
}
t = a[i];
a[i] = a[m];
a[m] = t;
}
return;
}
입력 : a[0], a[1] ... a[n-1]
Sorting이 완료된 후 저장된 값들을 b[0], b[1], ... b[n-1]이면
{a[0], a[1] ... a[n-1]} = {b[0], b[1], ... b[n-1]}
b[0] < b[1] < ... < b[n-1]
Invariant(불변) : k번째 루프가 끝났을 때
BASE : k = 0이면 P가 거짓이므로 true(vacouse true)
STEP :
k번째 루프가 끝났을 때
k+1번째 루프가 돌고 있을때
k+1번째 루프가 끝났을 때
재귀적인 선택정렬
int sort(int a[], int n)
{
int i, j, m, t;
m = 0;
for (i = 0; i < n; i++){
if(a[m] > a[j]) m = j;
}
t = a[0];
a[0] = a[m];
a[m] = t;
sort(a+1, n-1);
return;
}
BASE : n=1이면 1개를 정렬하는 것이므로 할 게 없음. TRUE
STEP :
n-1번째 루프가 끝났을 때 sort()함수가 성공한다면
n번째 루프가 끝났을 때 sort()함수가 성공한다.
int sort(int a[], int n)
{
int h;
int b[n];
h = n/2;
//copy a[] to b[]
sort(b,h);
sort(b+h, n-h);
// Merge two halves in b to a
return;
}
base : n=1. 할 일이 없음
step : n/2일때, 재귀 호출이 성공한다면
n일땐 재귀 호출이 성공한다.
b[0] < b[1] < ... <b[n/2] < b[n/2+1] < ... < b[n]이다.
int qsort(int a[], int n)
{
int d;
int p = a[0];
Rearrange a[] so that
//a[d] == p,
//a[0], a[1], ... , a[d-1] < p
//a[d+1], a[d+2], ..., a[n-1] > p
qsort(a,d);
qsort(a+d+1, n-d-1);
return;
}