새소식

컴퓨터공학 💻/알고리즘 분석

[알고리즘] 알고리즘 시간 복잡도 분석과 수행시간을 좌우하는 기준

  • -

알고리즘의 수행 시간을 좌우하는 기준은 다양하게 잡을 수 있습니다. 예를 들면 반복문의 반복 횟수, 특정한 행이 수행되는 횟수, 함수의 호출횟수 등이 있죠.

 

복잡도 분석 예시

Algorithm A Algorithm B Algorithm C
sum <- n*n; for i<-1 to n do
     sum <-sum + n;
for i<-1 to n do
    for j<-1 to n do
        sum <-sum + 1;

위 알고리즘의 연산 수는 각각 2번, 2n번, 3n^2+n번입니다. 대표로 1번, n번, n^2번이라고 합니다.

입력값이 커질 수록 상수를 더하거나, 뺴거나, 곱하거나, 나누는 연산은 알고리즘 복잡도에 영향력이 미미하기 때문입니다.

 

Algorithm C를 살펴봅시다.

# Algorithm C
for i<-1 to n do # 대입(<-): n번
    for j<-1 to n do # 대입(<-): n^2번
        sum <-sum + 1; # 대입(<-): n^2번, 더하기(+) : n^2번

맨 아래에서 sum에 1을 더하는 더하기 연산을 기준으로 복잡도를 분석하면 전체 알고리즘 복잡도를 n^2으로 정의할 수 있습니다. 그리고 같은 n^2번이 수행되는 다른 대입 연산들을 기준으로 분석해도 동일합니다.

 

하지만 가장 바깥의 반복문의 대입 연산을 기준으로 분석할 수는 없습니다. n번밖에 수행되지 않기 때문에 아무리 큰 상수를 곱해도 나중에는 나머지 연산들(n^2)의 연산 수를 넘어설 수는 없기 때문입니다.

그래서 3n^2 + n이나 n^2이나 복잡도 분석 측면에서 같다고 보는 것입니다.

 

몇가지 알고리즘을 더 살펴봅시다.

algorithm(A[], n) { 
        k = ⌊n/2⌋; 
        return A[k];     
}

연산 수는 총 4번입니다. 나누기 연산, 버림 연산, 대입 연산, 반환할 때의 연산이 들어간 것이죠. n에 관계 없이 상수 시간내에 완료됩니다.

algorithm(A[], n) { 
        sum ← 0; 
        for i ← 1 to n  
                sum← sum+ A[i];  
        return sum;      
}

연산 수는 총 3n+3, 대표로 n번입니다. 3과 +3는 영향력이 미미하기 때문입니다. 함수 실행, sum대입, 반복문 n번, sum대입 n번, sum더하기 n번, 반환의 연산이 들어간 것이죠. 

algorithm(A[], n) { 
        sum ← 0; 
        for i ← 1 to n                 
                for j ← 1 to n 
                        sum← sum+ A[i]*A[j]; 
        return sum;      
}

연산 수는 총 n^2번입니다.

algorithm(A[ ], n) { 
        sum ← 0; 
        for i ← 1 to n                 
                for j ← 1 to n { 
                        k ← A[1 ... n]에서 임의로 floor(n/2)개를 뽑을 때 이들 중 최댓값;  
                        sum ← sum + k; 
                } 
        return sum;      
}

연산 수는 n^3 + 4n^2 + n + 3, 대표로 n^3입니다. 연산 수가 n^2인 연산은 j대입, k대입, sum대입, sum + k입니다. 여기에 추가로 floor(n/2)개를 뽑는 경우와 이것 중 최댓값을 찾는 경우는 두 연산 모두 n^3 / 2 연산이 소요됩니다. 따라서 상수 연산을 무시한 n^3이 총 연산 수가 됩니다.

algorithm(A[], n) { 
        sum ← 0; 
        for i ← 1 to n-1                 
                for j ← i+1 to n 
                        sum← sum+ A[i]*A[j];         
        return sum;      
}

n이 7이라면 j가 몇번 반복할까요? 6 + 5 + 4 + 3 + 2 + 1 번 반복합니다. 이것을 n에 대한 식으로 바꾸면 (n-1) + (n-2) + ... + 1이 됩니다. 공식은 n(n-1)/2 이죠. 그래서 연산 수는 총 n(n-1)/2이며 대표로 n^2번입니다.

algorithm(n) { 
        if (n=1) return 1; 
        return n * algorithm(n-1); 
}

factorial 연산을 하는 알고리즘입니다. 함수 수행은 n번, if (n=1)은 n번, return 1은 1번 일어나며 밑의 return문은 모두 n-1번 일어납니다. 마지막 반환 시 return 1을 하므로 그 밑은 수행하지 않기 때문이죠. 연산 수는 총 n번입니다.

algorithm(A,n) {
  	tmp ← A[0];
  	for i←1 to n-1 do 
	        if tmp < A[i] then 
		tmp ← A[i];
  	return tmp;
}

배열에 들어있는 원소 중 최댓값을 구하는 알고리즘입니다. 연산 수는 n번입니다.

index algorithm(int n, keytype S[], keytype x) {
	index location
	location = 1;
	while (location <= n && S[location] != x)
		location++;
	if (location > n) location = 0;
	return location;
}

순차탐색 알고리즘입니다. 배열 S에서 index 1부터 index n까지 순차적으로 x를 찾아 위치를 반환합니다. 연산 수는 n번입니다.

int algorithm(int n){
  	index i; 
  	int f[] = new int[n+1];
  	f[0] = 0;
  	f[1] = 1;
  	if (n > 1)  {
     		for (i=2; i<=n; i++)
        		f[i] = f[i-1] + f[i-2];
  	}
 	return f[n];
}

n위치의 피보나치 수를 구하는 알고리즘입니다. 연산 수는 n번입니다.

void algorithm(int n, keytype S[]) {
		index i, j;

		for (i = 1; i <= n-1; i++) 
			for (j = i+1; j <= n; j++)
				if ( S[j] < S[i] ) exchange S[i] and S[j];
}

배열 S를 교환 정렬하는 알고리즘입니다. 수행시간을 좌우하는 기준은 안쪽 for문에 있는 연산 전체이며 총 연산 수는 n(n-1)/2, 대표로 n^2입니다.

index algorithm(int n, keytype S[], keytype x) {
  index location, low, high, mid;
  low = 1;
  high = n;
  location = 0;
  while ( low <= high && location == 0 ) {
        mid = ⌊(low + high) / 2⌋;
        if (x == S[mid]) location = mid;
        else if (x < S[mid]) high = mid - 1;
        else low = mid + 1;
  }
  return location;
}

배열 S에서 x를 찾는 이분 탐색 알고리즘입니다. 수행시간을 좌우하는 기준은 while문에 있는 연산 전체이며 이 알고리즘은 반복 수행마다 n을 반씩 나누어 계산하기 때문에 최악의 경우인 데이터가 1개 남는 경우를 고려하면 총 연산 수는 (1/2)^k * n = 1이 되며 양변에 2^k를 곱하면 n = 2^k, 다시 양변에 log를 취하면 logn = k가 됩니다.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.