원글 : https://janghw.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Divide-and-Conquer-%EB%B6%84%ED%95%A0%EC%A0%95%EB%B3%B5

c0smicb0y

[알고리즘] Divide and Conquer (분할정복) 본문

프로그래밍/알고리즘

[알고리즘] Divide and Conquer (분할정복)

2015.01.11 00:55

분할정복

정의 : 분할정복 알고리즘은 문제를 나눌 수 없을 때까지 나누어서 각각을 풀면서 다시 합병하여 문제의 답을 얻는 알고리즘이다.


알고리즘을 설계하는 요령

(1) Divide : 문제가 분할이 가능한 경우, 2개 이상의 문제로 나눈다.

(2) Conquer : 나누어진 문제가 여전히 분할이 가능하면, 또 다시 Divide를 수행한다. 그렇지 않으면 문제를 푼다.

(3) Combine : Conquer한 문제들을 통합하여 원래 문제의 답을 얻는다.


  • 문제를 제대로 나누면 Conquer하는 것은 쉽기 때문에 Divide를 제대로 하는 것이 가장 중요하다.
  • 분할정복 알고리즘은 재귀 알고리즘이 많이 사용되는데, 이 부분에서 분할정복 알고리즘의 효율성을 깎아내릴 수 있다.


분할정복의 응용

1. 병합 정렬 (Merge Sort)

헝가리 출신 미국인 수학자인 존 폰 노이만이 1945년에 개발한 알고리즘이다. 시간복잡도는 이고, 공간복잡도는 이다.


알고리즘

1. 정렬할 데이터 집합의 크기가 0또는 1이면 이미 정렬된 것으로 보고, 그렇지 않으면

2. 데이터 집합을 반으로 나눈다.

3. 원래 같은 집합에서 나뉘어져 나온 데이터 집합 둘을 병합하여 하나의 데이터 집합으로 만든다. 단, 병합할 때 데이터 집합의 원소는 순서에 맞춰 정렬한다.

4. 데이터 집합이 다시 하나가 될 때까지 3을 반복한다.


예를 들어 다음과 같은 데이터 집합이 있다고 해보자.



알고리즘의 1~2 과정을 반복하여 나눈다. 색칠한 부분은 나눈 기준이 되는 부분이다.




분할을 했으니 정복을 할 차례다. 3~4과정을 반복해서 수행한다.


조각난 데이터 집합을 정렬해가면서 병합하면 결국 완전히 정렬된 하나의 데이터 집합을 얻는 알고리즘이다.


그런데 병합이 어떻게 정렬하면서 합칠 수 있는 것일까.


두 데이터 집합을 정렬하면서 합치는 방법

1. 두 데이터 집합의 크기의 합만큼의 크기를 가지는 빈 데이터 집합을 만든다.

2. 두 데이터 집합의 첫 번째 요소들을 비교하여 작은 요소를 빈 데이터 집합에 추가한다. 그리고 새 데이터 집합에 추가한 요소는 원래 데이터 집합에서 삭제한다.

3. 원래 두 데이터 집합의 요소가 모두 삭제될 때 까지 2를 반복한다.


예를 들어 다음과 같은 데이터 집합 A, B와 이 두 데이터 집합의 크기의 합만큼의 크기를 가지는 빈 데이터 집합인 C가 있다고 하자.


두 데이터 집합의 첫 번째 요소를 비교한다. A의 첫 번째 요소는 2, B의 첫번째 요소는 1이므로 B의 것이 더 작다. C에 1을 추가하고 B에서 1을 삭제한다.


그 다음 A의 2와 B의 3을 비교한다. 2가 작으니 C에 2를 추가하고 A에서 2를 삭제한다.


A의 5와 B의 3을 비교한다. 3이 작으니 C에 3을 추가하고 B에서 3을 삭제한다.

이렇게 데이터 A와 B의 요소들을 비교해서 C에 넣고 A와 B의 각 요소들을 삭제해나가다보면, A에 9하나만 남게된다. B에는 비교할 요소가 남아 있지 않으므로 그냥 9를 C에 추가하고 A에서 9를 삭제한다. 이로써 A와 B의 요소들이 모두 삭제되었고 C는 정렬된 데이터 집합이 되었다.


2. 거듭 제곱 (Exponentiation)

n거듭 제곱은 자신을 n번 곱해야 함으로 의 시간이 소요된다. 이것을 개선하기 위해서 연산방법을 조금 바꾸어보자.


은 다음과 같이 정의되지만,

다음과 같이 표현할 수도 있다.

을 구할 때 C를 8번 곱하지 않고 를 구한 뒤 제곱을 두 번 더 반복하면 결국 세번의 연산만으로 같은 결과를 얻을 수 있다.

지수가 짝수일때는 지수를 반으로 나눠서 곱한다.

지수가 홀수일때는 지수에서 1을 빼고 반으로 나누어서 곱하고 밑을 한번 더 곱하면 된다.


이렇게 지수를 반으로 나눠가는 거듭 제곱 알고리즘의 수행 시간은 이다.


3. 피보나치 수열 (Fibonacci Sequence)

피보나치 수열은 다음과 같이 정의된다.


이 알고리즘으로는 걸리는 시간은 이다.

연산을 줄이기 위해 2차 정사각행렬을 이용한다.


 이므로 n제곱을 하게 되면



  이렇게 거듭제곱 알고리즘때와 비슷하게 나타낼 수 있다.

수행 시간 또한 거듭 제곱 알고리즘과 똑같은 이 걸리게 된다.



0 Comments
댓글쓰기 폼

'C Lang > algorithm' 카테고리의 다른 글

자료구조, 알고리즘 - 성능측정 (Big-O)  (0) 2019.06.17
삽입정렬  (0) 2019.06.17
알고리즘이란?  (0) 2019.06.17

원글 : https://wayhome25.github.io/cs/2017/04/20/cs-26-bigO/



강의노트 25. 자료구조, 알고리즘 - 성능측정 (Big-O)


패스트캠퍼스 컴퓨터공학 입문 수업을 듣고 중요한 내용을 정리했습니다. 개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.

성능측정 - Big-O Notation

reference

Big-O Notation

시간복잡도

  • 실행 시간 이라는 관점에서 알고리즘의 효율을 측정한다.
  • 문제를 해결하려고 할 때마다 시간복잡도를 분석하는 습관을 들이면 좋은 개발자가 될 수 있다.
  • 이 때 중요한건 연산자의 숫자 혹은 연산 과정(step)의 수
  • 예를 들어 N이라는 인자가 주어졌을 때, 1부터 N까지를 더하는 함수 sumOfN을 정의한다.
def sumOfN(N):
  theSum = 0
  for i in range(1,N+1):
    theSum += i

  return theSum
  • 함수 sumOfN 에서 주요 연산 횟수를 살펴보면 아래와 같다.
def sumOfN(N):
  theSum = 0 # 1번만 실행된다.
  for i in range(1,N+1):
    theSum += i # N번 수행된다.

  return theSum
  • 함수 sumOfN 의 시간 복잡도는 아래 같이 표시할 수 있다.
    • T(n) = 1 + n
    • 파라미터 n 은 문제의 사이즈를 의미
    • T(n) 은 사이즈가 n 인 문제를 푸는데 걸리는 시간을 의미, 여기서는 즉 1 + n steps를 말한다.

Big-O

  • 그럼 문제의 사이즈 n에 따라서 알고리즘 수행 시간은 어떻게 달라질까?
  • 문제의 사이즈가 커질수록, 최고 차항의 차수가 결과에 가장 많은 영향을 미친다. (상기 예시에서는 1+n 중 n을 의미한다)
  • 이처럼 시간 복잡도에 가장 큰 영향을 미치는 차항으로 시간복잡도를 나타내는 것을 Big-O 표기법 (Big-O Notation) 이라고 하며, O(f(n)) 과 같이 표기한다. (O는 order 라고 읽는다.)
  • 시간 복잡도의 표현 법 중 가장 많이 쓰이는 표현 법으로 알고리즘 실행 시간의 상한을 나타낸 표기법이다. (최악의 효율)  
  • 간단한 Big-O 표기법의 예
T(n)=n^2+2n+9 # O(n2)

T(n)=n^4+n^3+n^2+1 # O(n4)

T(n)=5n^3+3n^2+2n+1 # O(n3)

worst case, average case, best case

  • 문제의 사이즈 보다, 데이터가 무엇이냐가 알고리즘의 성능에 더 영향을 미치기도 한다.
  • Big-O는 대부분 최악의 경우를 표현한다. (quick sort의 경우 예외적으로 average case를(O(N log N)) Big-O로 사용한다.)
def pick5(li):     # li = [5,3,1] 인 경우 1번만 반복
  for i in li:     # li = [1,5] 인 경우 2번 반복
    if i == 5:
      return

Big-O 표기법의 종류와 성능

Big-O 표기법의 종류

스크린샷 2017-04-30 오후 2.45.57

  • O(1) - (상수) Constant
    • 입력되는 데이터양과 상관없이 일정한 실행 시간을 가진다.
    • 알고리즘이 문제를 해결하는데 오직 한 단계만 거친다.
  • O(logN) Logarithmic
    • 데이터양이 많아져도, 시간이 조금씩 늘어난다.
    • 시간에 비례하여, 탐색 가능한 데이터양이 2의 n승이 된다.
    • 문제를 해결하는데 필요한 단계들이 연산마다 특정 요인에 의해 줄어든다.
    • 만약 입력 자료의 수에 따라 실행 시간이 이 log N 의 관계를 만족한다면 N이 증가함에 따라 실행시간이 조금씩 늘어난다. 이 유형은 주로 커다란 문제를 일정한 크기를 갖는 작은 문제로 쪼갤때 나타나는 유형이다.
    • Binary Search
  • O(N) Linear
    • 데이터양에 따라 시간이 정비례한다.
    • linear search, for 문을 통한 탐색을 생각하면 되겠다.
  • O(N log N) log linear
    • 데이터양이 N배 많이 진다면, 실행 시간은 N배 보다 조금더 많아 진다. (정비례 하지 않는다.)
    • 이 유형은 커다란 문제를 독립적인 작은 문제로 쪼개어 각각에 대해 독립적으로 해결하고,나중에 다시 그것들을 하나로 모으는 경우에 나타난다. N이 두배로 늘어나면 실행 시간은 2배보다 약간 더 많이 늘어난다.
    • 퀵소트, 머지소트
  • O(N^2) Quadratic
    • 데이터양에 따라 걸리는 시간은 제곱에 비례한다. (효율이 좋지 않음, 사용하면 안된다)
    • 이 유형은 이중루프내에서 입력 자료를 처리 하는 경우에 나타난다. N값이 큰값이 되면 실행 시간은 감당하지 못할 정도로 커지게 된다.
    • 문제를 해결하기 위한 단계의 수는 입력값 n의 제곱
    • 2중 for 문을 사용하는 버블소트, 삽입정렬(insertion sort)

if문과 big-o

if문은 bigO에 영향을 미칠까 미치지 않을까 라는 의문이 들것이다. 답부터 말하면 no이다. 물론 if문이 많아지면 어플의 성능 자체에 영향을 미치는 것은 자명하다. 하지만 bigO지수는 인풋 사이즈n에 의해 측정하는 시간의 복잡도를 나타내는 개념이므로 절대적인 성능을 나타내는 수치는 아니다. 따라서, if는 성능상에는 영향을 미치지만 bigO수치에는 영향을 미치지 않는다.

성능비교

  • 성능 순서 : O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)bigo_graph스크린샷 2017-04-30 오후 2.54.04

실습

big-O 를 계산해보자!

  • 각 연산의 시간 복잡도를 계산하고 최종 시간복잡도를 big-O 로 표시해본다.
    a=5
    b=6
    c=10
    for i in range(n):
     for j in range(n):
        x = i * i
        y = j * j
        z = i * j
    for k in range(n):
     w = a*k + 45
     v = b*b
    d = 33
    
  • 각 연산 횟수를 더하면 시간 복잡도는 아래와 같다
  • T(n) = 3 + 3ㅜㅜn^2 + 2n + 1 = 3n^2 + 2n + 4
  • 이를 Big-O 로 표현하면 O(n^2) 이다.
    a=5 # Constant 1
    b=6 # Constant 1
    c=10 # Constant 1
    for i in range(n):
     for j in range(n):
        x = i * i # n^2 => for문 중첩 사용
        y = j * j # n^2
        z = i * j # n^2
    for k in range(n):
     w = a*k + 45 # n
     v = b*b # n
    d = 33 # Constant 1
    

self check

Write two Python functions to find the minimum number in a list. 
The first function should compare each number to every other number on the list. O(n^2). 
The second function should be linear O(n)

O(n^2)

# (O^2)
import time
from random import randrange


def findMin(alist):
    overallmin = alist[0]
    for i in alist:
        issmallest = True
        for j in alist: # O(n^2) => for문 중첩
            if i > j:
                issmallest = False
        if issmallest:
            overallmin = i
    return overallmin

# 확인
print(findMin([5,2,1,0]))
print(findMin([0,2,3,4]))

# 연산시간 (O(n^2))
for listSize in range(1000, 10001, 1000):
    alist = [randrange(100000) for _ in range(listSize)]
    start = time.time()
    print(findMin(alist))
    end = time.time()
    print("size: {}, time: {}".format(listSize, end-start))

O(n)


def findMin(alist):
    minsofar = alist[0]
    for i in alist: # O(n) alist의 길이만큼 반복, loop 중첩 없음   
        if i < minsofar:
            minsofar = i
    return minsofar

# 확인
print(findMin([5,2,1,0]))
print(findMin([0,2,3,4]))

# 연산시간 (O(n))
for listSize in range(1000, 10001, 1000):
    alist = [randrange(100000) for _ in range(listSize)]
    start = time.time()
    print(findMin(alist))
    end = time.time()
    print("size: {}, time: {}".format(listSize, end-start))

reference




'C Lang > algorithm' 카테고리의 다른 글

분할정복 알고리즘  (0) 2019.06.18
삽입정렬  (0) 2019.06.17
알고리즘이란?  (0) 2019.06.17

원글 : https://www.zerocho.com/category/Algorithm/post/57e39fca76a7850015e6944a


안녕하세요. 이번 시간에는 삽입 정렬에 대해 알아보겠습니다. 알고리즘에는 많은 유형이 있지만 주로 탐색과 정렬하는 것들이 많습니다. 탐색을 하기 전에는 미리 정렬을 해야 합니다. 정렬된 자료를 탐색하는 강력한 알고리즘이 있거든요. 정렬하는 방법도 여러가지가 있습니다. 그 중에 오늘은 삽입 정렬 차례입니다.



여러 개의 섞인 숫자가 있을 때, 그 숫자를 작은 순서부터 큰 순서로 정렬하는 겁니다. 삽입 정렬은 첫 숫자는 놔두고, 두 번째 자리 숫자부터 뽑아서 그 숫자가 첫 숫자보다 크면 첫 숫자 오른쪽에, 작으면 왼쪽에 넣습니다. 세 번째 자리 숫자를 뽑아서 앞의 두 숫자와 크기를 비교해서 알맞은 자리에 넣습니다. 이렇게 끝까지 계속하면 정렬됩니다.



쉽게 설명하면 오른손에 쥔 정렬되지 않은 카드를 왼손에 정렬한다고 생각하시면 됩니다. 하나를 뽑아서 왼손에 놓고, 다음 카드는 먼저 뽑은 카드들과 비교하여 알맞은 위치에 넣어줍니다. 이렇게하면 결국 왼손에는 정렬된 카드만 남습니다.



코드로 보면 어려우니까 배열이 움직이는 과정을 쉽게 예를 하나 들어서 순서대로 봅시다. 크게 두 가지 과정이 있습니다. 첫째는 다음 숫자를 선택하는 과정이고, 둘째는 선택한 숫자를 이미 정렬된 숫자들과 비교해 알맞은 위치에 넣는 과정입니다.

[5, 6, 1, 2, 4, 3]과 같은 배열이 있습니다.


첫 번째는 넘어가고 두 번째부터 시작합니다. 숫자 6을 선택하고
[56, 1, 2, 4, 3]


앞의 5와 비교하는데 5보다 크기 때문에 그냥 그 자리에 둡니다. 다음 숫자 1을 선택합니다.
[5, 61, 2, 4, 3]


1은 앞의 5, 6보다 작기 때문에 5, 6 앞에 넣어줍니다. 다음 숫자 2를 선택합니다.
[1, 5, 62, 4, 3]


2는 1보다는 크고, 5와 6보다는 작기 때문에 그 사이에 넣어줍니다. 다음 숫자 4를 선택합니다.
[1, 2, 5, 64, 3]


마찬가지 과정으로 1,2와 5,6 사이에 넣어줍니다. 다음 숫자 3을 선택합니다.
[1, 2, 4, 5, 63]


마지막으로 1,2와 4,5,6 사이에 3을 넣고, 다음 숫자가 없으므로 종료합니다.

[1, 2, 3, 4, 5, 6] 정렬이 완료되었습니다. 



이제 코드로 만들어볼까요? 배열(array)을 받는 함수(insertionSort)를 만듭시다. 일단 뽑을 숫자의 위치를 선택할 변수(i)가 필요할 것 같습니다. 그리고 뽑은 숫자 값을 저장할 변수(temp)도 필요하고요. 뽑은 숫자를 알맞은 위치에 넣을 때 사용할 변수(j)도 있어야할 것 같네요.

var insertionSort = function(array) {
  var i = 1, j, temp;
  for (i; i < array.length; i++) {
    temp = array[i]; // 새로운 숫자를 선택함
    for (j = i - 1; j >= 0 && temp < array[j]; j--) { // 선택한 숫자를 이미 정렬된 숫자들과 비교하며 넣을 위치를 찾는 과정, 선택한 숫자가 정렬된 숫자보다 작으면
      array[j + 1] = array[j]; // 한 칸씩 뒤로 밀어낸다
    }
    array[j + 1] = temp; // 마지막 빈 칸에 선택한 숫자를 넣어준다.
  }
  return array;
};
insertionSort([5, 6, 1, 2, 4, 3]); // [1, 2, 3, 4, 5, 6]

코드가 복잡합니다. 설명을 위해 console을 넣었습니다. 잘 보면 크게 두 과정을 반복하고 있음을 알 수 있습니다. 숫자를 선택한 후 알맞은 위치에 넣습니다. 알맞은 위치에 넣는 과정이 좀 헷갈리는데, 정렬된 숫자들 중 큰 숫자부터 비교하면서 선택한 숫자가 정렬된 숫자보다 클 때까지 반복하고, 커지면 그 사이에 집어넣습니다. 즉, 선택한 숫자보다 큰 숫자들은 오른쪽으로 한 칸씩 밀어내고 빈 자리에 선택한 숫자를 넣는 겁니다.

삽입 정렬은 성능이 뛰어난 정렬은 아닙니다. 작은 수에서만 효과적인 경우가 많습니다. 첫 번째로 배우는 이유는 간단하기 때문입니다. 또한 이미 정렬되어 있는 배열에 새로운 원소를 집어넣어 다시 정렬할 때는 매우 효과적입니다. 새 원소를 처음부터 한번씩만 기존의 원소들과 비교하면 되니까요.


보통 반복문을 두 번 중첩해서 돌면 성능이 그리 좋지 않습니다. 정렬이 효과적인지 아닌지 판단하려면 복잡도라는 개념을 알아야합니다. 삽입 정렬은 다른 정렬에 비해 복잡도가 높기 때문에 효과적이지 못하다고 평가받습니다.

정렬을 평가할 때, 복잡도라는 개념을 사용한다고 했습니다. 주로 정렬하는 데 걸리는 시간의 복잡도를 따집니다. 정렬을 할 때 얼마나 시간이 걸리는 지가 중요한 데 이를 평가하는 기준으로 여러가지 표기법이 있습니다. 이것들을 다음 시간에 알아보겠습니다.


'C Lang > algorithm' 카테고리의 다른 글

분할정복 알고리즘  (0) 2019.06.18
자료구조, 알고리즘 - 성능측정 (Big-O)  (0) 2019.06.17
알고리즘이란?  (0) 2019.06.17

원글 : https://www.zerocho.com/category/Algorithm/post/57e22a778d6bcf0015231c8b

 

알고리즘

알고리즘이란 어떤 값을 입력받아 다른 값을 출력하는 계산 절차를 의미합니다. 간단하죠? 정의는 간단하지만, 실제로는 그렇지 않습니다... 예전에 순서도 그려보셨나요? 그 과정과 비슷합니다. 결과값을 만들기 위한 과정을 설명합니다.

알고리즘은 위의 정의처럼 출력값을 얻기 위한 절차이기 때문에 코딩을 해본 사람이라면 모두 경험해보았을 겁니다. 물론 이 강좌에서 다루는 알고리즘은 조금 복잡한 문제들입니다만 여러분들도 이미 알고리즘을 사용하여 코딩을 하고 있습니다. 겁먹을 필요는 없다는 거죠.

실생활에서도 알고리즘이 많이 사용됩니다. 인터넷에서 검색을 할 때, 검색 알고리즘이라는 말 많이 들어보셨죠? 어떻게 하면 검색어(입력값)으로부터 정확한 검색 결과(출력값)를 도출해낼 것인지 결정하는 과정입니다. 구글은 수십억 개의 문서들 중에 정확한 결과를 0.몇초만에 가져옵니다.

구글에 zerocho를 쳐보세요. 링크

외계인을 고문해서 얻은 기술인 걸까요? 어떻게 수백억 개의 데이터 중에 3만개의 검색 결과를 0.53초만에 찾을 수 있었을까요? 그것도 대충 찾은 게 아니라 상당한 정확도로 말이죠. 바로 엄청 정교하면서도 효율적인 알고리즘을 만든 겁니다.

다른 예로, 제 블로그에 사용된 SHA512 암호화(정확히는 해시 함수) 기법같은 것도 비밀번호(입력값)을 어떻게 암호(출력값)로 바꿀지 정하는 것이고요. 알고리즘은 생각보다 실생활에 밀접한 존재입니다.

대부분의 경우, 보통 사람들도 쉽게 문제 해결을 위한 알고리즘을 짤 수 있지만, 특정 상황에 가면 머리가 아파오기 시작합니다. 수백, 수천 개의 데이터를 어떻게 정렬할 것인지, n번째로 큰 수는 어떻게 찾을 것인지 등 사람이 직접 하면 간단한 것들도, 컴퓨터에게 시키려니 너무 어렵습니다. 이럴 때는 구글이나 네이버 지식인에 물어보곤 하죠. 이 강좌에서는 알아두고 있으면 편리한 알고리즘들을 소개할 겁니다. 

알고리즘 수학이 매우 중요합니다. 알고리즘을 분석할 때 수행시간을 주로 따지는 데, 그것을 계산할 때 수학이 사용됩니다. 그렇지만 이 강좌에서는 최대한 수학적인 것은 쉽게 설명해보도록 노력하겠습니다. 대학교 과정이라 정말 어렵습니다. 저도 수학 별 거 아니겠지하고 무시했다가 큰 코 다쳤던 기억이 나네요. 기존보다 더 효율적인 알고리즘을 찾아보세요. 만약 찾는다면 여러분에게 엄청난 명예를 안겨다줄 겁니다. 실제로 알고리즘 하나를 개발해내면 박사 학위정도는 그냥 받을 수 있습니다.

우리가 배울 알고리즘을 시각적으로 보여주는 유튜브 영상입니다. 멍하니 보기 좋습니다.

다음 시간부터 다양한 알고리즘들에 대해 알아보겠습니다! 첫 시간은 삽입 정렬입니다.

'C Lang > algorithm' 카테고리의 다른 글

분할정복 알고리즘  (0) 2019.06.18
자료구조, 알고리즘 - 성능측정 (Big-O)  (0) 2019.06.17
삽입정렬  (0) 2019.06.17

+ Recent posts