출처: https://www.whatwant.com

 

이번에는 Greedy Algorithm을 살펴보도록 하겠다.

 

앞서 살펴본 Dynamic Programming의 경우에는 모든 경우의 수를 고려하여 최적의 솔루션을 찾는 방식이었다면

Greedy Algorithm은 어떤 순간에 최선으로 보이는 것을 선택해서 문제를 푸는 방식이다.

 

뭔 말이냐고!? 일단 하나 살펴보자.

 

 

[Overview]

어떤 활동(activity)들의 집합이 있다고 해보자.

 

출처: https://www.whatwant.com

 

이처럼 여러 activities들이 있을 때,

서로 겹치지 않고 동시에 할 수 있는 activities 중 가장 큰 subset을 선택하는 문제는 어떻게 풀면 좋을까?

 

예를 들자면,

강의실이 하나만 있을 때 여러 수업들을 어떻게 배치해야 가장 많은 수업들을 배치할 수 있는지를 정하는 것과 같다.

 

[Sample]

예시를 하나 들어보자.

 

출처: https://www.whatwant.com

 

위와 같이 activity들이 있다고 했을 때 어떤 조합들이 가능할까?!

 

출처: https://www.whatwant.com

 

우선 {a3, a9, a11} 집합의 경우 mutually compatible 하게 잘 묶여 있다.

서로 배타적이면서 같이 어울릴 수 있는 조합이라는 말이다.

 

하지만, 이 집합은 주어진 상황에서 largest set이라고 할 수는 없다.

 

출처: https://www.whatwant.com

 

{a1, a4, a8, a11} set은 largest set이라고 할 수 있다.

4개보다 더 큰 조합은 찾을 수 없기 때문이다.

 

하지만 unique한 largest set은 아니다.

 

출처: https://www.whatwant.com

 

{a2, a4, a9, a11}의 경우도 largest set이다.

 

그런데, 이렇게 눈으로 정답을 찾는 것은 뭔가 좀..... ^^

 

[Optimal substructure]

일단 아래와 같은 문제가 주어졌다고 했을 때, 각 activity들의 시작 위치와 종료 위치를 작성해보자.

 

출처: https://www.whatwant.com

 

여기에서는 문제가 index 순서에 따라 종료 시간(f)이 순서대로 나와 있어서 정렬을 할 필요가 없지만,

종료 시간의 오름차순으로 정렬까지 해줘야 한다.

 

출처: https://www.whatwant.com

 

이렇게만 했으면 나머지는 쉽다.

 

출처: https://www.whatwant.com

 

제일 먼저 종료되는 시간 이후로 첫번째로 시작하는 activity를 선택하고,

그 activity가 종료되는 시간을 확인하고 또 다시, 그 종료 시간 이후로 시작하는 activity를 찾고... 반복하면 된다.

 

끝이다.

 

뭔가 허무한데, 끝이다.

 

 

[Elements of the greedy strategy]

어째서 Dynamic Programming과 다르게 Greedy Algorithm은 이렇게 문제를 풀 수 있는지에 대해서 알아보자.

 

출처: https://www.whatwant.com

 

subproblem을 모두 풀어야지만 솔루션을 선택할 수 있는 경우에는 Dynamic Programming 문제이고,

모든 subproblem을 풀기 전에 솔루션을 선택할 수 있는 문제는 Greedy Algorithm을 적용할 수 있다.

 

출처: https://www.whatwant.com

 

Zero-One 배낭 문제는 물건을 갯수 단위로만 가방안에 넣을 수 있는 경우에 해당하는 문제이다.

물건들의 가격과 무게가 모두 다르다고 했을 때, 어떻게 넣어야지 가장 비싸게 물건을 훔칠 수 있는지를 찾는 것이다.

모든 조합을 고려해야 문제가 풀리는 것이다.

 

출처: https://www.whatwant.com

 

이와는 다르게, 만약 아이템을 조각내서 가방에 담을 수 있다고 하면 어떻게 될까?

그러면 개별 단가가 가장 비싼 것들로 정렬해서 그냥 그렇게 넣으면 된다.

담을 수 있는 무게와 딱 떨어지지 않을 경우에는 조각내면 된다.

모든 조합을 고려할 필요가 없는 것이다.

 

예시를 들어보자.

 

출처: https://www.whatwant.com

 

⒜와 같이 상황이 주어졌다고 해보자.

배낭은 담을 수 있는 무게의 한계가 있다.

 

⒝는 Dynamic Programming 방식으로 해답을 찾아보는 것이다. 모든 조합을 검토하는 것이다.

 

⒞는 Greedy Algorithm 방식이다. 무게당 단가를 계산해서 가장 비싼 것들부터 넣기 시작하다가

무게 제한에 걸리는 시점에는 해당 아이템을 잘라내서 무게에 맞추면 되는 것이다.

 

확실히 문제의 유형이 다르다!!!

반응형

출처: https://www.whatwant.com

 

이번에 살펴볼 Dynamin Programming 알고리즘은

Matrix-Chain Multiplication (연쇄 행렬 곱셈)이다.

 

[Background]

우선 알아야할 내용은 바로 "행렬 곱" 규칙이다.

 

 

'행렬 곱'을 하기 위해서는 앞 행렬의 column 길이와 뒤 행렬의 row 길이가 같아야 한다.

그러면, 앞 행렬의 row 와 뒤 행렬의 column 길이만큼의 결과가 나온다.

 

두번째로 알아야할 내용은 행렬 곱에서 '결합 법칙'이 성립한다는 것이다.

 

 

그런데, 여기에서 우리가 주의깊게 살펴봐야할 사실이 하나 등장한다.

 

행렬 곱에서 결합을 어떻게 하는지에 따라 계산 횟수가 달라진다는 것이다.

 

 

위의 예시와 같이 3개의 행렬이 주어졌을 때,

계산 횟수를 살펴보면 10배의 차이가 발생한다는 것을 알 수 있다.

 

 

[Problem]

그러면 우리가 여기에서 해결하고자 하는 문제가 무엇일까?

 

 

곱셈을 해야하는 여러 행렬이 순서대로 주어졌을 때,

어떻게 결합을 해야 전체 계산 횟수를 최소화 할 수 있을지를 찾아내는 것이다.

 

 

[Brute force approach]

일단 무식하게 살펴보자.

 

 

총 4개의 행렬이 주어졌을 때, 총 5가지의 묶음 방법이 존재한다.

 

이를 분류해보면

앞에서부터 1개 묶음을 하면 총 2종의 결합 방법이 존재하고

2개 묶음을 하면 1종,

3개 묶음을 하면 2종의 결합 방법이 있기에 총 5종의 결합 방법이 있게 된다.

 

이 과정을 공식으로 살펴보면 다음과 같다.

 

 

공식을 유도하고 분석하는 것은 생략하도록 하겠다.

 

그런데, 이를 Asymptotic 하게 생각해보면, 다음과 같다.

 

 

마찬가지로 계산식 유도 및 분석은 생략하고.... 결론적으로 엄청난 경우의 수가 나오기에...

이렇게 문제를 푸는 것은 말이 안됨! ^^

 

[Solution]

똑똑하게 문제를 풀어보자.

 

 

지금까지 살펴본 내용을 공식화한 것으로 생각하면 된다.

위 공식을 가지고 직접 손으로 계산을 해보자.

 

행렬에 따라서 row와 column은 다음과 같이 살펴볼 수 있다.

 

 

왜 이렇게 표현이 되는지 이해가 안되면 안된다.

앞에서 충분히 살펴본 내용을 곰곰히 생각해보면 된다.

 

예시를 하나 들어보자.

행렬이 6개가 주어졌다고 했을 때, p값이 다음과  같이 제시 되었다고 해보자.

 

 

이 문제를 풀기 위해 우리는 m, s 행렬을 작성해야 한다.

 

 

왜 이렇게 하는지는 문제를 풀어보면서 알아보자.

 

 

위 공식을 살펴보면 알겠지만,

i=j 조건에서는 모두 0 값을 갖게 되고 i보다 j는 항상 커야 되기 때문에 표의 아랫부분은 계산하지 않아도 된다.

 

 

m[1][2] 값부터 계산해보면 위와 같이 정리해볼 수 있다.

k값은 ( i ≤ k < j ) 조건이므로 ( 1 ≤ k < 2 )와 같고, 그렇기 때문에 k값은 1밖에 갖을 수 없다.

 

s 행렬의 s[1][2] 값은 k값이 1이므로 1값을 작성하면 된다.

 

이런식으로 m[2][3], m[3][4], m[4][5], m[5][6] 부분을 채울 수 있다.

 

 

이번에는 m[1][3]을 계산해보자.

 

앞에서와 달리 k값은 ( 1 ≤ k < 3 )이므로, k값은 1, 2 의 2가지 경우가 있을 수 있다.

그러므로, 최종 결과가 2개 나오고, 그 중에서 min 값을 선택하면 된다.

 

k값이 1인 경우를 선택했으므로 s[1][3] 위치에는 1을 작성하면 된다.

 

 

하나씩 하나씩 이렇게 채워나가면 된다.

 

전부 채우면 다음과 같다.

 

 

끝~

 

[Pseudo Code]

이 과정을 가지고 pseudo code로 만들어보자.

 

 

이렇게 만들어진 결과물을 출력하기 위한 code는 다음과 같다.

 

 

[Time & Space]

time complexity 와 memory space를 계산해보면 다음과 같다.

 

먼저 Time Complexity를 살펴보자.

 

 

무려 n의 3승 이다.

 

메모리 공간을 계산해보면 다음과 같다.

 

 

알고리즘을 위해 추가로 사용하는 메모리는 m, s 배열이 필요하다.

Asymptotic 하게 해보면.... 결국은 n의 제곱만큼의 공간을 필요로 한다.

 

반응형

출처: https://www.whatwant.com

 

[ Terminology & Problem ]

이번에 살펴보고자 하는 알고리즘은 "Longest Common Subsequence (LCS)" 이다.
어떤 문제를 해결하고자 하는 것인지 설명하기 전에 용어에 대한 정의부터 살펴보겠다.

 

출처: https://www.whatwant.com

 

여기에서 주의깊게 살펴봐야 할 것은 "Substring"인데, 연속적인 character를 의미한다.

 

출처: https://www.whatwant.com

 

반면, "Subsequence"는 비연속적인 character 순서를 포함한다.

 

출처: https://www.whatwant.com

 

2개의 string이 주어졌을 때, 공통적으로 있는 subsequence를 "Common Subsequence"라고 한다.

그리고 "Common Subsequence" 중에서 가장 긴 길이를 갖는 것을 "Longest Common Subsequence (LCS)"라고 한다.

 

이런 LCS를 구하는 방법을 찾는 것이 이번 목표이다 ^^

 

[Brute force approach]

착실하게 (무식하게?) 찾는 방법은 다음과 같다.

 

출처: https://www.whatwant.com

 

당연히 이렇게 찾는 것은 말이 안된다.

입력 string의 갯수가 n개라고 했을 때 2의 n승만큼 subsequence가 나오기 때문이다.

 

[Notation]

이후 설명할 내용에서 사용하는 notation을 정리하면 다음과 같다.

 

 

[Idea]

똑똑하게 해결하기 위한 아이디어는 다음과 같다.

 

출처: https://www.whatwant.com

 

복잡해보이는데, 가만히 살펴보면 엄청 당연한 이야기를 하고 있다.

 

특정 위치의 chracter가 같다면 LCS는 그 같은 값은 일단 LCS에 포함되는 것이고,

각 입력 string에서 동일한 값보다 1씩 작은 string의 LCS값을 더해주면 전체 LCS가 된다.

 

반면, 특정 위치의 chracter가 같지 않다면 LCS는 입력 string중 하나의 값을 1 차감한 내역으로 다시 LCS를 구하면 된다.

 

 

[ Longest Common Subsequece - Dynamic Programming Algorithm ]

위의 아이디어를 정리하면 다음과 같은 알고리즘이 된다.

 

 

이걸 어떻게 하는 것인지 살펴보자.

 

다음과 같은 2개의 string이 입력되었을 때 다음과 같은 표를 그리고 초기화 하자.

  - X = ABCBDAB

  - Y = BDCABA

 

출처: https://www.whatwant.com

 

앞에서 정리한 알고리즘을 기반으로 하나씩 값을 채워나가면 된다.

 

출처: https://www.whatwant.com

 

① x ≠ y 이므로 둘 중 큰 값 선택해야 하는데, 둘 모두 0이므로, 0

② ③ 상동

④ ⑥ x = y 이므로, 왼쪽 대각선 위의 값 + 1

⑤ x ≠ y 이므로 둘 중 큰 값 선택해야 하는데, ④ 값이 1이므로, 1

 

그리고, 어느 곳에서 값을 받아갔는지 화살표로 표기 해줘야 한다.

 

이런식으로 하나씩 값을 작성해 나가면 다음과 같이 된다.

 

출처: https://www.whatwant.com

 

LCS값이 어떻게 되었는지는 다음과 같이 추적해볼 수 있다.

 

출처: https://www.whatwant.com

 

화살표를 보면 알겠지만, 정답이 하나만 있는 것은 아니다.

 

출처: https://www.whatwant.com

 

[Pseudo Code]

손으로 진행했던 것을 코드로 만들어 보자.

 

출처: https://www.whatwant.com

 

손으로 진행했던 것과 차이가 있는 부분 위주로만 설명해봤다.

위 표에서 화살표를 살짝 끄적였는데, code에서는 b array를 통해 기록하도록 했다.

 

결과를 출력하기 위한 코드는 다음과 같다.

 

 

[Time & Space]

code를 보면 직관적으로 파악 가능한 것 처럼 Time complexity는 다음과 같다.

 

 

Space는 b array + c array 공간이 추가적으로 필요하기에 다음과 같다.

 

 

그런데, Space를 줄일 수 있는 경우가 있다.

실제 String 정보까지는 필요없고, 길이값만 필요하다고 하면 다음과 같이 작은 space만 필요로 한다.

 

 

또 다른 경우로는 다음과 같은 case가 있을 수 있다.

 

출처: https://www.whatwant.com

 

LCS에 대해서는 여기까지~

반응형

라이선스 이슈를 피해서 Anaconda 사용하는 방법에 대해서 이미 포스팅을 했었다.

  - 회사에서 Anaconda 사용하기 (Miniconda + conda-forge)

 

 

그런데, Ubuntu 운영체제를 기준으로 작성했다보니

Windows 환경을 사용하는 분들의 불만 아닌 불만이 있는 것 같아서... Windows 버전도 추가로 작성해본다.

 

① Miniconda 설치

② conda-forge 등록

③ conda 가상환경 생성

④ Jupyter Notebook 설치 

 

 

 

① Miniconda 설치

  - Windows 버전의 Miniconda3 Installer를 다운로드 받아서 설치하면 된다.

    . https://docs.anaconda.com/free/miniconda/

 

Miniconda

 

  - 약 78MB 정도의 용량의 설치파일이 다운로드 된다.

 

Installer

 

  - 이후 옵션은 모두 default로 해서 설치를 진행하도록 하자 (경로 등록 등도 일단 그냥 무시하자)

 

 

Complete

 

  - 우리는 설명서 따위는 보지 않는 ...... ^^

 

② conda-forge 등록

  - Windows 키보드를 눌러보면 새로 설치된 앱을 확인할 수 있다.

 

실행

 

  - 본인 취향대로 골라서 실행하면 되지만, 여기에서는 밑에 있는 "Anaconda Prompt (miniconda3)"를 선택했다.

 

prompt

 

  - 명령어는 다음과 같다.

(base) C:\Users\whatw>conda config --add channels conda-forge

(base) C:\Users\whatw>conda config --set channel_priority strict

(base) C:\Users\whatw>conda config --show channels
channels:
  - conda-forge
  - defaults

 

③ conda 가상환경 생성

  - 일단 conda 가상환경을 생성해야 한다. 이 때, 사용할 Python 버전을 지정하면 된다.

 

conda

 

  - 중간에 계속 설치를 진행할 것인지 묻는 부분이 나오는데, 그냥 엔터 때리면(?) 된다.

(base) C:\Users\whatw>conda create -n p39 python=3.9.15
Channels:
 - conda-forge
 - defaults
Platform: win-64
Collecting package metadata (repodata.json): done

...

done
#
# To activate this environment, use
#
#     $ conda activate p39
#
# To deactivate an active environment, use
#
#     $ conda deactivate

 

  - 설치 완료 부분을 보면 가상환경을 실행하는 방법과 종료하는 방법을 알려준다.

 

④ Jupyter Notebook 설치

  - 앞에서 생성한 가상환경 內 Jupyter Notebook을 설치해야하기에 우선 가상환경을 실행하자.

 

active

 

  - 프롬프트 앞 부분을 보면 현재 실행중인 환경 정보를 알려준다. 항상 신경쓰자.

 

  - 이제는 Jupyter Notebook 패키지를 설치하자.

(p39) C:\Users\whatw>pip install jupyter notebook ipykernel
Collecting jupyter
  Downloading jupyter-1.0.0-py2.py3-none-any.whl.metadata (995 bytes)
Collecting notebook
  Downloading notebook-7.1.2-py3-none-any.whl.metadata (10 kB)

...

 

  - 커널도 등록하자

(p39) C:\Users\whatw>python -m ipykernel install --user --name p39 --display-name "python 3.9"
Installed kernelspec p39 in C:\Users\whatw\AppData\Roaming\jupyter\kernels\p39

 

  - 'Jupyter Notebook'이 실행될 경로를 하나 만든 뒤, 그 곳에서 실행하자.

 

execute

 

  - 갑자기 웹브라우저가 아래와 같이 실행되면 성공한 것이다.

 

jupyter notebook

 

  - 오른쪽 "New" 버튼을 통해 notebook을 생성하자.

 

hello

 

잘 된다!

 

반응형

출처 : https://www.whatwant.com

 

이번에 살펴볼 것은 한글로 말하자면, "막대 자르기" 알고리즘이다.

어떤 문제를 해결하기 위한 알고리즘인지 알아보자.

 

[Problem]

n만큼의 길이를 갖고 있는 막대기가 있다고 하자.

 

출처 : https://www.whatwant.com

 

이 막대기를 잘라서 팔아야 하는데, 길이에 따라서 판매되는 가격이 다르다고 했을 때

어떻게 잘라서 팔아야 최대 이익이 나오는지를 찾아내야 하는 것이 문제이다.

 

 

위와 같이 막대기 길이에 따른 수익은 제시가 된다.

알아야 할 notation 규칙은 다음과 같다.

 

 

[Brute force approach]

"n = 4" 만큼의 길이를 갖고 있는 막대기가 있고, 길이에 따른 가격은 다음과 같다.

 

출처 : https://www.whatwant.com

 

이 때, 나올 수 있는 모든 경우의 수를 다 살펴보자면 다음과 같다.

 

출처 : https://www.whatwant.com

 

최대 수익을 얻기 위해서는 ⑶번과 같이 2개 묶음으로 나누는 경우가 10만큼의 수익을 얻을 수 있다.

그런데, 이처럼 모든 경우의 수를 나열해서 모두 계산해보기에는 너무 비효율적이다.

 

[Rod-Cutting Algorithm]

일단 다음과 같은 계산식을 살펴보자.

 

 

n 길이의 막대기가 있을 때, i 길이의 가격과  'n - i' 길이일 때의 최대 수익의 합이 최대인 i값을 찾는 것을 의미 한다.

 

출처 : https://www.whatwant.com

 

다음과 같은 문제가 주어졌다고 해보자.

 

 

r[i], s[i] 값은 다음과 같이 계산해볼 수 있다.

 

 

그러면 다음과 같은 결과를 얻을 수 있다.

 

 

이렇게 만들어진 표가 있다면,

8 길이만큼의 막대기가 주어졌을 때 다음과 같이 활용할 수 있다.

 

출처 : https://www.whatwant.com

 

[Pseudo Code]

 

 

[Time & Space]

이 알고리즘에서는 r[n], s[n] 값을 저장하기 위한 메모리 공간을 필요로 한다.

길이 n 값에 선형으로 비례하는 만큼 사용하기 때문에 다음과 같이 정리할 수 있다.

 

 

소요 시간은 Pseudo Code를 보면 중복 for문이 사용되는 부분을 잘 살펴봐야 하는데,

다음과 같이 정리 해볼 수 있다.

 

 

수고하셨습니다 !!!

반응형

출처: https://www.whatwant.com

 

왠지 이름만 들어도 어려울 것 같은 느낌이 팍팍 드는, Dynamic Programming !!!

 

연구비를 타내기 위해 그냥 있어보이려고 멋지게 작명하다보니 이런 이름을 가졌다고 한다.

사람 사는게 다 그렇지 뭐~ ^^

 

"Dynamic Programming"은 뭔가 계산하는 알고리즘이라기 보다는

작은 문제를 풀어서 그 중간 결과를 기록해 놓음으로써 다시 재계산하지 않도록 하는 방법이다.

 

여러 유형의 문제를 Dynamic Programming으로 푸는 것을 살펴볼텐데

그 첫번째로 살펴볼 것이 바로 "Assembly-line scheduling" 이다.

 

 

[Definition]

'Assembly-line'이라는 것에 대한 정의 부터 살펴보자.

 

출처: https://ko.dict.naver.com/#/entry/koko/db51c8c4d1144a24b1d8103e3ebd94e3

 

컨베이어벨트를 따라 단계별로 부품 조립하는 것을 생각하면 된다.

 

출처: https://www.whatwant.com

 

- station : 조립 단계. 제품 조립은 n개의 station을 거치면 완성이 된다.

- a : 각 station 단계에서 소요되는 시간

- line : 하나의 컨베이어 벨트로 생각하면 된다.

- e : line에 조립을 시작하도록 하기 위해 소요되는 시간

- x : 조립 완료된 제품을 전달하기 위해 소요되는 시간

- t : line을 옮기기 위해 소요되는 시간

 

 

[Problem]

여러 line으로 구성된 assembly-line이 있을 때,

어떤 경로가 가장 짧은 소요 시간으로 조립할 수 있는지 최적의(가장 빠른) 경로를 찾는 방법은?

 

 

[Solution-1]

가장 확실하고 무식한(?) 방법으 모든 경우의 수를 구해서 찾는 방법이다.

 

하지만, 말 그대로 무식한 방법이기에 ... 경우의 수가 너무 크다.

 

 

 

[Solution-2]

스마트한 방법을 찾아보자.

 

▷ Idea

어느 시점(S)에서 자기 순서까지 올 수 있는 방법은 2가지 경우 밖에 없다.

 

출처: https://www.whatwant.com

 

자기가 있는 동일한 line의 직전 단계에서 오는 방법과, 다른 line의 직전 단계에서 오는 2가지의 경우이다.

 

 

그러면, 그 2가지의 경우 중에 가장 빠른 것을 선택하면 되게 되는 것이다.

 

▷ How to

그러면 예시를 살펴보자.

 

출처: https://www.whatwant.com

 

각 단계에서 소요되는 시간을 더해가면서 정리하면 된다.

보다 더 짧은 시간을 선택하면 되기 때문에 □로 표기된 값들이 선택된 결과들이다.

 

이렇게 선택하는 과정에서 소요되는 Running Time은 어떻게 될까?

각 Station에서 2개씩 값을 보면 되기 때문에 2n의 Running Time이 필요하고,

entry + exit 과정이 있기 때문에 이를 정리하면 다음과 같다.

 

 

Brute-force 방법으로 할 때에는 어느 시점(S)에서 자기한테 까지 오는 모든 경우의 수를 살펴보지만

지금 방식에서는 바로 직전의 2가지 경우만 살펴보기 때문에 n에 비례하는 복잡도를 갖게 되었다.

 

 

그러면, 중간에 각 S단계에 따른 값들을 어떻게 저장하면 될까?

 

 

위와 같은 표 내용만 기록/기억 하고 있으면 되는 것이다.

그런데, 이 것만 가지고는 어떤 경로로 구성된 것인지 알기가 어렵다.

 

출처: https://www.whatwant.com

 

이 내용을 기록할 표가 하나 더 필요한 것이다.

 

 

그런데, 2개의 테이블이 모두 필요할까?

 

경로만 확인 가능해도 되는 경우에는 L table만 있으면 된다.

S table은 현재 값과 직전 값만 알면 되고, 최적해(S* = 38) 값만 저장하면 된다.

 

S table에서 S3를 계산할 때 S2 값만 알면 된다. 즉, S1의 내용은 필요 없다.

메모리를 굳이 table 전체 크기만큼 잡아 놓지 않아도 된다는 것이다.

 

 

L table 값만 알면 최적 경로를 찾을 수 있기 때문이다.

 

반대로 S 값을 갖고 있는 table만 있어도 L 값 table 없이 경로를 찾아낼 수는 있다.

하지만 L 값 table을 사용하는 것이 훨씬 더 편하기에 L Table을 이용하는 것이다.

 

 

▷ Pseudo Code

코드로 구현해보면 다음과 같다.

 

출처: https://www.whatwant.com

 

뭔가 복잡해보이는데, 살펴보기 편하게 구분해보면 다음과 같다.

 

출처: https://www.whatwant.com

 

결과를 출력하는 부분은 아래와 같이 구현해볼 수 있다.

 

 

실행 결과는 다음과 같이 나올 것이다.

 

 

 

▷ Space consumption

일단 기본적으로는 S table과 L table 모두 필요로 한다는 전제 하에 다음과 같은 메모리 사용량을 필요로 한다.

 

 

 

▷ Running time

하나의 요소 別 소요 시간이 Θ(1) 소요시간이 걸린다고 했을 때, 전체 소요 시간은 Θ(n)이라고 할 수 있다.

 

 

 

뭔가 심오한 Dynamic Programming의 세계이다~

반응형

출처: https://www.whatwant.com

 

"Radix"가 뭘까?

'기수(基數)'는 0~9까지 기본이 되는 수를 의미하는데, 여기에서는 '진법(進法)'의 의미가 더 큰 것 같다.

 

https://en.dict.naver.com/#/entry/enko/17ef262637ad4f04a48f2e6a0186dbbd

 

"Radix Sort"의 다른 호칭은 다음과 같다.

- 기수 정렬

- Bucket Sort

- Digital Sort

 

Radix Sort 알고리즘도 역사가 오래된 아이인데, 1923년에 이미 천공카드 분류 방법으로 널리 사용되었단다.

(어!? 천공카드가 뭔지 아는 것 만으로도 년식 인증인가!?)

 

 

① MSD (Most Significant Digit,  최상위 유효숫자)

  - 앞(큰) 자리 부터 정렬을 해 나가는 방식이다.

 

출처: https://www.whatwant.com

 

  - 중간에 가로로 있는 구분선 위치 정보는 중요하다.

  - 단계 별로 구분선 안에서만 정렬이 이루어져야 하기 때문이다.

 

② LSD (Least Significant Digit, 최하위 유효숫자)

  - MSD 방식과 정반대로 뒤(작은) 자리 부터 정렬을 해보자.

 

출처: https://www.whatwant.com

 

  - Stable 이라는 속성을 정말 잘 이용한 방법으로 보인다.

  - MSD와 다르게 가로줄(각 단계에서의 구분 위치) 정보를 관리할 필요가 없다.

  - 아랫 자리들이 이미 sorting이 되어있기 때문에 윗 자리 sorting을 하면 전체가 sorting 된다.

 

③ Pseudo Code

  - 너무 추상적일 수 있지만, 일단 다음과 같이 Pseudo Code를 정리할 수 있다.

 

출처: https://www.whatwant.com

 

  - 여기에서 중요한 것은 "stable sort" 방식이어야 한다는 점이다.

 

④ Running Time

  - 우리는 바로 전에 훌륭한 stable sort 알고리즘 중 하나를 공부했었다 → Counting Sort !!!

  - Counting Sort 알고리즘을 이용해서 Radix Sort를 해본다고 가정하면, 다음과 같다.

 

출처: https://www.whatwant.com

 

  - 위 내용을 정리하자면, 다음과 같다.

 

출처: https://www.whatwant.com

 

⑤ Changing d and k

  - 우리가 지금까지 다루는 방식은 다음과 같다.

출처: https://www.whatwant.com

 

  - 하지만, 이것을 좀 다르게 처리할 수도 있다.

 

출처: https://www.whatwant.com

 

  - 응?! 10진법이 아니라 100진법(진수)으로 처리할 수도 있다는 것이다.

 

⑥ optimal r

  - 일단 아래 그림을 잘 살펴보고 각 변수값의 정의를 확인하자.

  - n개의 b-bit 숫자가 있다고 했을 때, r 묶음으로 처리를 하는 경우이다.

 

출처: https://www.whatwant.com

 

  - 이럴 때, 우리는 다음의 최적해 r 값을 찾아야 하는 것이다.

 

출처: https://www.whatwant.com

 

  - 2 가지 경우로 나누어서 생각해보자.

 

출처: https://www.whatwant.com

 

  - 위의 경우는 입력값 개수에 비해서 b값이 많이 작을 경우이다.

    . 이 때는 굳이 나누지 말고 통으로 (하나로) 묶어서 정렬을 해도 충분하다는 의미이다.

 

  - 다른 경우는 다음과 같다.

 

출처: https://www.whatwant.com

 

  - 입력값 개수보다 값 자체가 길면, 좀 나누어서(log n 정도) 정렬을 하는 것이 좋다는 의미이다.

 

⑦ extra memory

  - 앞에서 살펴봤겠지만, 일단 Radix Sort는 in place 알고리즘은 아니다.

 

⑧ memory copy

  - d 값만큼 정렬 step이 발생하는데, 이 때마다 값들을 복사해야하는데 이로 인한 cost가 크다.

 

 

 

뭔가 좀 어수선하게 설명이 되었는데,

전체적인 흐름은 파악이 되었을거라 기대해본다.

반응형

출처: https://www.whatwant.com

 

이번에 살펴볼 Sorting algorithm은 앞에서 공부한 것들과 궤를 달리한다.

 

지금까지 공부한 것은 비교(comparison) 기반의 정렬이었지만

이번에는 counting 방식으로 정렬을 하는 계수 정렬(Counting sort)이다.

 

① basic counting sort

  - 비교 행위 없이 입력값 하나씩 읽어가며 개수만 세는 것으로 정렬이 된다.

 

출처: https://www.whatwant.com

 

② extra memory

  - 개수를 세기 위한 C(count array)가 추가로 필요하다. (= in place 알고리즘이 아니다!)

 

③ No stable

  - 입력값의 순서가 유지되는 것을 "stable 하다"고 한다.

 

  - 위 그림에서 3개의 "3" 입력값에 대해서 살펴보면,

    . (빨간색) index 값으로 표현해보면 '3-3', '6-3', '8-3'이라고 할 수 있다. 각 '3'은 다른 '3'이다.

    . 이 때, 출력값(output array)에서 '5-3', '6-3', '7-3'이 있는데,

    . 입력값의 순서가 유지된 것이라고 할 수 있을까?

 

출처: https://www.whatwant.com

 

  - Counting sort는 stable 할 수 없는 algorithm 이다.

 

④ Stabled(Advanced) Counting Sort

  - Counting sort 를 Stable 하도록 만들 수 있다.

 

  - Encoding ..... 기존 count array를 기반으로 누적값을 저장하는 C' array를 만들어줘야 한다.

 

출처: https://www.whatwant.com

 

  - Decoding ..... 누적값을 갖고 있는 C' array와 입력값(A array)를 가지고 출력값(B array)를 만들어준다.

    . 입력값의 제일 끝에서 부터 역으로 하나씩 처리하는 방식이다.

 

출처: https://www.whatwant.com

 

    . 하나 더 해보면, 다음과 같다.

 

출처: https://www.whatwant.com

 

    . 그 다음 "3" 값을 주의 깊게 봐야 한다.

      왜냐하면, 처음에 했던 "3"과 이번에 해야하는 "3"은 다른 "3"이기 때문이다.

 

출처: https://www.whatwant.com

 

    . 뭔가 신기하게 보일 수도 있긴 하지만... 그래서 뭐?

    . 입력값(A array)의 3의 순서에 따라 출력값(B array)의 순서가 정해진다. → Stable 하다 !!!

 

⑤ Pseudo code

  - 왠지 복잡해보이지만, 복잡하지 않은 코드이다.

 

출처: https://www.whatwant.com

 

  - 변수 (메모리) 모습을 그려보면 다음과 같다.

 

출처: https://www.whatwant.com

 

  - 여기에서 k는 입력값의 종류(개수)를 의미한다.

  - 코드를 분석해보면 다음과 같다.

 

출처: https://www.whatwant.com

 

⑥ Running Time

  - 실행 시간은 선형이다.

 

출처: https://www.whatwant.com

 

  - 정리해보면 다음과 같다.

 

출처: https://www.whatwant.com

 

 

그 이름에 걸맞게 입력값을 하나씩 세기만 하면 되는 시간인 것이다 !!!

즉, 앞에서 살펴본 "Lower bounds for (comparison) sort" 제약에 해당하지 않는다 !!!

 

반응형

+ Recent posts