CODE

제 2강 빅 오 표기법을 이용한 성능 향상과 활용 방법

빅 오 표기법을 이용한 성능 향상

빅 오 표기법이란 알고리즘 평가 단계를 표기하는 방법이다. 여러가지 알고리즘을 비교 검사하여 보다 효율적인 방법을 찾아 프로그램의 효율을 높일 수 있다.

중복 검사 알고리즘

O(N²) 알고리즘
function hasDuplicatedValue1(array) {
    for (let i = 0; i < array.length; i++) {
        for (let j = 0; j < array.length; j++) {
            if (i != j && array[i] === array[j]) {
                console.log('true');
                return true;
            }
        }
    }
    console.log('false');
    return false;    
}

const array = [1, 2, 3, 4, 5, 6, 6];
hasDuplicatedValue(array);

예를 들어 배열의 중복된 값을 검사하는 알고리즘을 검사한다고 생각해 보자. 해당 알고리즘을 빅 오 표기법으로 표기한 결과는 O(N²)이다. 이를 그래프로 그리면 다음과 같다.

위 그래프를 보면 이 알고리즘이 얼마나 비효율적 인지를 알 수 있다. 이처럼 이중 for문이 사용되는 알고리즘의 경우 위 그래프와 같이 그려지기 때문에 대체로 이중 for문을 사용한 알고리즘은 비효율적이라고 할 수 있다.

O(N) 알고리즘

빅 오 표기법으로 해당 알고리즘이 비효율적이라는 것을 발견하면 이를 대체할 수 있는 방법을 생각하는 것이 좋다. 물론 이를 대체할 수 있는 방법이 없을 수도 있다. 위 알고리즘의 경우 이중 for문을 사용하지 않고 O(N)으로 해결할 수 있는 방법이 있기 때문에 알고리즘을 다음과 같이 수정할 수 있다.

function hasDuplicatedValue2(array) {
    const existNumber = new Array(100);
    existNumber.fill(0);
    for (let i = 0; i < array.length; i++) {
        if (existNumber[array[i]] === 0) {
            existNumber[array[i]] = 1;
        } else {
            console.log('true');
            return true;
        }
    }
    console.log('false');
    return false;    
}

const array2 = [1, 2, 3, 4, 5, 6, 6];
hasDuplicatedValue2(array2);

 

이처럼 O(N²)과 O(N)을 그래프로 나타내면 해당 알고리즘이 효율면에서 얼마나 차이가 나는지를 알 수 있다.

(참고) 2N과 N²의 차이

2N
    for (let i = 0; i < array.length; i++) {
       ...
    }
    for (let j = 0; j < array.length; j++) {
       ...
    }
    for (let i = 0; i < array.length; i++) {
        for (let j = 0; j < array.length; j++) {
            ...
        }
    }

 

빅 오 표기법을 활용하는 방법

위와 같이 중첩 for문을 사용한 알고리즘 O(N²)과 사용하지 않은 알고리즘 O(N)은 확연하게 효율면에서 차이가 난다는 것을 한 눈으로 봐도 알 수 있다. 하지만 빅 오 표기법의 지수를 무시하는 특성상 O(N² / 2)를 O(N²)로 표기하며 O(100N)도 O(N)로 표기한다. 확연하게 차이가 나는 알고리즘임에도 불구하고 빅 오 표기법을 사용하면 차이를 두지 않는다. 그러나 빅 오 표기법으로는 같은 표기법을 사용하더라도 분명히 효율성의 차이가 나기 때문에 이를 비교하여 알고리즘을 선택할 수 있어야 한다.

같은 분류의 알고리즘 비교

같은 분류의 알고리즘이란 같은 지수를 가지고 있어 빅 오 표기법으로는 똑같이 표기되는 것을 말한다. 정렬하는 알고리즘인 버블 정렬과 선택 정렬을 비교하여 빅 오 표기법이 같은 알고리즘(O(N))이라도 보다 효율적인 알고리즘을 선택하는 방법을 배워두자.

버블 정렬

버블 정렬이란 배열의 요소를 하나씩 비교하며 큰 숫자를 오른쪽으로 이동시키는 정렬 알고리즘이다.

  1. 첫 번째와 두 번째를 비교한다.
  2. 더 큰 수를 오른쪽으로 이동시킨다.
  3. 두 번째와 세번째를 비교하고 더 큰 수를 오른쪽으로 이동시킨다.
  4. 마지막 요소를 비교하면 가장 큰 수가 오른쪽으로 이동해 있다.
  5. 정렬된 마지막 요소를 제외하고 다시 사이클을 반복한다.
  6. 첫 번째 요소가 정렬되면 종료한다.

Javascript
// 버블 정렬 (오름차순)
function AscBubbleSort(array) {
    for (let i = 0; i < array.length; i++) {
        for (let j = 0; j < array.length - 1; j++) {
            if (array[j] > array[j + 1]) {
                let temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
    console.log('array', array);
    return array;
}

// 버블 정렬 (내림차순)
function DescBubbleSort(array) {
    for (let i = 0; i < array.length; i++) {
        for (let j = 0; j < array.length - 1; j++) { 
            if (array[j] < array[j + 1]) {
                let temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
    console.log('array', array);
    return array;
}

const array = [5, 4, 3, 2, 1];
AscBubbleSort(array);
DescBubbleSort(array);
Go
package main

import "fmt"

// 버블 정렬 (오름차순)
func AscBubbleSort(array []int) []int {
	for i := 0; i < len(array)-1; i++ { 
		for j := 0; j < len(array)-1; j++ {
			if array[j] > array[j+1] {
				array[j], array[j+1] = array[j+1], array[j]
			}
		}
	}
	return array
}

// 버블 정렬 (내림차순)
func DescBubbleSort(array []int) []int {
	for i := 0; i < len(array)-1; i++ { 
		for j := 0; j < len(array)-1; j++ { 
			if array[j] < array[j+1] {
				array[j], array[j+1] = array[j+1], array[j]
			}
		}
	}
	return array
}

// 버블 정렬 (오름차순) 2
func AscBubbleSort2(array []int) []int {
	swapped := true
	for swapped {
		swapped = false
		for i := 0; i < len(array)-1; i++ {
			if array[i] > array[i+1] {
				array[i], array[i+1] = array[i+1], array[i]
				swapped = true
			}
		}
	}
	return array
}

func main() {
	array := []int{5, 4, 3, 2, 1, 0}
	fmt.Println(AscBubbleSort(array))
	fmt.Println(DescBubbleSort(array))
	fmt.Println(AscBubbleSort2(array))
}

버블 정렬의 알고리즘의 최악의 시나리오를 정리하면 다음과 같다.

비교 교환
1cycle 4 4
2cycle 3 3
3cycle 2 2
4cycle 1 1

버블 정렬 알고리즘은 O(N²)의 효율성과 비교했을 때 거의 비슷하다고 할 수 있다. 즉, 해당 알고리즘이 효율적이지 못 하다는 것을 알 수 있다. 정렬 알고리즘은 어떻게든 이중 for문을 사용할 수 밖에 없다. 그럼에도 불구하고 우리는 효육적으로 프로그램을 가동하기 위해 보다 나은 알고리즘을 생각해야만 한다. 대표적이 예로 선택 정렬이 있다.

원소 N개 최대 단계수
5 20 25
10 90 100
20 380 400
40 1560 1600
80 6320 6400
선택 정렬

선택 정렬이란 최소값을 검색하고 한번만 이동하는 정열 알고리즘이다.

  1. 주어진 배열 중에서 최소값을 찾는다.
  2. 첫번째 값과 최소값의 위치를 교환한다.
  3. 첫번째 값을 뺀 나머지 배열을 같은 방법으로 교환한다.
  4. 하나의 원소만 남을 때까지 위의 1~3 과정을 반복한다.

Javascript
// 선택 정렬 (오름차순)
function AscSelectedSort(array) {
    for (let i = 0; i < array.length-1; i++) {
        let minIndex = i;
        for (let j = i; j < array.length; j++) {
            if (array[j] < array[minIndex]) {
                let temp = array[j];
                array[j] = array[minIndex];
                array[minIndex] = temp;
            }
        }
    }
    console.log('array', array);
    return array
}

const array1 = [5, 4, 3, 2, 1, 0];
AscSelectedSort(array1);

// 선택 정렬 (내림차순)
function DescSelectedSort(array) {
    for (let i = 0; i < array.length-1; i++) {
        let minIndex = i;
        for (let j = i; j < array.length; j++) {
            if (array[j] > array[minIndex]) {
                let temp = array[j];
                array[j] = array[minIndex];
                array[minIndex] = temp;
            }
        }
    }
    console.log('array', array);
    return array
}

const array2 = [0, 1, 2, 3, 4, 5];
DescSelectedSort(array2);
Go
func selectedSort(array []int) []int {
	count := 0
	for i := 0; i < len(array)-1; i++ {
		minIndex := i
		for j := i; j < len(array); j++ {
			if array[j] < array[minIndex] {
				minIndex = j
				count++
			}
		}
		array[i], array[minIndex] = array[minIndex], array[i]
	}
	fmt.Println(array)
	fmt.Println(count)
	return array  
버블 정렬과 선택 정렬 비교하기
원소 개수 버블 정렬 최대 단계 수 선택정렬 최대 단계 수
5 20 14(10번 비교 + 4번 교환)
10 90 54(45번 비교 + 9번 교환)
20 380 199(180번 비교 + 19번 교환)
40 1560 819(780번 비교 + 39번 교환)
80 6320 3238(3160번 비교 + 79번 교환)

버블 정렬과 선택 정렬 모두 빅 오 표기법으로 나타내면 O(N)이지만 두 알고리즘을 정확히 비교하면 위 표처럼 확연하게 차이가 있다는 것을 알 수 있다. 이처럼 빅 오 표기법은 서로 다른 분류라면 쉽게 비교할 수 있지만 같은 분류의 알고리즘이라면 보다 효율적인 알고리즘을 선택할 수 있어야한다.

최신글