for textmining

P, NP 문제 (2)

|

이번 글에서는 P/NP 문제에 대해 살펴보도록 하겠습니다. 이 글은 고려대 김선욱 교수님 강의를 정리했음을 먼저 밝힙니다. 이 문제에 대한 개괄적인 내용은 이곳을 참고하시면 좋을 것 같습니다. 그럼 시작하겠습니다.

용어 정리

용어 및 개념 몇 가지 정리하고 넘어가겠습니다(이해를 돕기 위한 비공식적인 정의).

  • Class $P$란 다항시간(polynomial time) 내에 풀리는 문제를 가리킵니다.
  • Class $NP$란 운이 좋으면 다항시간에 풀리는(nondeterministic polynomial) 문제로, 어떤 답이 해당 문제의 정답인지 여부를 다항시간 내에 확인(verified)할 수 있는 문제를 가리킵니다.
  • $NP$-hard란 모든 $NP$ 문제를 해당 문제로 바꿔풀 수 있을(reduce) 때 해당 문제를 가리킵니다.
  • $NP$-complete란 $NP$-hard이면서 Class $NP$에 속하는 문제를 가리킵니다.
  • Reducibility란 어떤 문제 $P$를 $Q$로 바꿔풀 수 있는 경우를 가리킵니다. 만일 $P$가 $Q$로 reduce할 수 있다면, $P$는 $Q$보다 해결하기가 어렵지 않다고 말할 수 있습니다.

$P$, $NP$, $NP$-complete 사이의 관계는 다음과 같습니다.

위 다이어그램을 바탕으로 다음과 같은 사실을 확인할 수 있습니다.

  • $NP$ 바깥의 영역은 어떤 답이 해당 문제의 정답인지 여부를 다항시간 내에 확인할 수 없는 문제들을 가리킵니다. 다시 말해 어떤 case가 맞는지조차 알기 어려운, 미지의 문제라는 뜻입니다.
  • $P≠NP$-complete입니다.
  • $NP$ 문제는 다항시간 내에 풀 수 있는지 없는지 아직 모르는 문제를 가리킨다면, $NP$-complete는 절대 다항시간 내에 풀 수 없다는 게 증명된 문제를 가리킵니다.

내 알고리즘이 어떤 부류인지 판단하기

내가 만든 알고리즘 $L$이 $NP$-complete인지 아닌지 판단하는 일반적인 절차는 다음과 같습니다. 이미 알려져 있는 $NP$-complete를 지렛대 삼아 판별하는 방법입니다.

  • $L$이 $NP$임을 증명 : 내가 만든 $L$에 대한 어떤 답이 $L$의 정답인지 여부를 다항시간 내에 확인할 수 있는지를 체크

  • $L$이 $NP$-hard임을 증명

    (1) 내가 만든 $L$과 가장 유사한, 이미 증명된 $NP$-complete 문제를 찾는다.

    (2) $L$을 (1)에서 찾은 $NP$-complete 문제로 바꾸고, 그 역도 가능한 함수 $f$를 정의한다.

    (3) (2)에서 정의한 $f$가 다항시간 내에 작동하는지를 증명한다.

판별 예시

내가 만든 알고리즘 $L$이 $NP$-complete인지 아닌지 판단하는 예시(그림 출처)를 들어보겠습니다. 레지스터 할당에 쓰이는 Interference Graph를 $L$이라고 칩시다. 이것이 $NP$-complete인지 여부를 알아내는 것이 목표입니다.

Interference Graph란 코드 시퀀스에서 둘 이상의 변수가 겹쳐 등장할 경우 엣지를 연결해두는 그래프입니다. 둘 이상의 변수를 한 레지스터에 동시에 할당하는 걸 피하기 위해서입니다.

예컨대 변수 $A$는 line 1부터 line 5까지, $B$는 line 2부터 line 3까지 살아 있어야 하고, 2~3 line에서 둘이 겹치기 때문에 Interference Graph에서 둘 사이에 엣지를 연결해 줍니다. 이렇게 만들어진 Interference Graph에서 이웃한 노드들끼리는 서로 다른 레지스터에 변수를 할당해주는 겁니다.

우선 $L$이 $NP$임을 증명해 봅시다. Interfence Graph가 주어져 있을 때, 서로 이웃한 노드들끼리 다른 레지스터에 속해있는지 확인하면 되므로, $O(V+E)$의 계산복잡성이 듭니다. 다항시간에 풀 수 있다는 얘기입니다. 따라서 $L$은 $NP$입니다.

$L$이 $NP$-complete임을 증명해 봅시다. 그래프 컬러링(graph coloring) 문제는 이미 증명된 $NP$-complete 문제입니다. 다음과 같이 함수 $f$를 정의하면 $L$을 그래프 컬러링 문제로 바꿀 수 있습니다.

  • Interfernce Graph의 노드 → Colored Graph의 노드
  • Interfernce Graph의 엣지 → Colored Graph의 엣지
  • Interfernce Graph의 노드에 할당된 레지스터 → Colored Graph의 노드 컬러

다음과 같이 함수 $f$를 정의하면 그래프 컬러링 문제를 $L$로 바꿀 수 있습니다.

  • Colored Graph의 노드 → Interfernce Graph의 노드
  • Colored Graph의 엣지 → Interfernce Graph의 엣지
  • Colored Graph의 노드 컬러 → Interfernce Graph의 노드에 할당된 레지스터

위 함수 $f$의 경우 그래프의 노드와 엣지는 그대로 두고, 컬러 정보를 레지스터로 바꾸면 됩니다. 심지어 컬러를 레지스터라고 봐도 무방할 정도죠. 어쨌거나 $L$을 그래프 컬러링 문제로, 그래프 컬러링 문제를 $L$로 다항시간 내에 바꿀 수 있습니다. 따라서 $L$은 $NP$-complete입니다.

Comment  Read more

P, NP 문제 (1)

|

P/NP 문제에 대해 명철하고 해박하게 서술한 글을 소개해 보겠습니다. 서울대 컴퓨터공학부 이광근 교수님께서 쓰신 컴퓨터 과학이 여는 세계의 일부를 그대로 옮겨 왔습니다. 워낙 좋은 책이라 저 자신 또한 정리 목적으로 타이핑해 올려놓은 것이나 문제가 될 경우 바로 삭제하도록 하겠습니다. (밑줄 및 강조 표시는 제가 해놓은 것입니다)

현실적

쓸만한 알고리즘. 그 비용이 견딜 만하면 현실적인 알고리즘이다. 알고리즘의 실행비용(복잡도)이 입력 크기($n$)에 대해 상수($k$)승을 가지면($n^k$) ‘현실적’이라고 본다. 예를 들어 $O(n^2)$ 혹은 $O(n^{3.5})$나 $O(n^{100})$ 등이다. 입력 크기가 커지면 다항으로 증가하는 비용(polynomial complexity)이다.

모든 게 순간이었다고 말하지 마라. 달은 윙크 한 번 하는 데 한 달이나 걸린다. - 이정록, ‘더딘 사랑’

왜 다항이 ‘현실적인’ 비용인가? 그런 알고리즘은 컴퓨터 성능이 곱하기로 빨라지면($pו$) 곱하기로 이득을 본다($p’ו$). 알고리즘 비용이 $n^k$라고 하자. 컴퓨터 성능이 $p$배 빨라지면 같은 입력에 대해서 비용이 $n^k/p$로 준다. 따라서 예전과 같은 시간에 처리할 수 있는 입력의 크기(즉 $n^k=m^k/p$인 $m$은) $p^{1/k}×n$으로 늘릴 수 있다. $p^{1/k}$배 커진 입력을 같은 시간에 처리할 수 있게 되는 셈이다.

디지털 컴퓨터 성능은 약 2년마다 2배씩 성능이 좋아졌다. 알고리즘 비용이 $O(n)$이라 하자. 그러면 2년 후 2배 빨라진 컴퓨터로는 2배 큰 입력을 같은 시간에 처리할 수 있게 된다. 비용이 $O(n^2)$이라면? $\sqrt{2}$배 큰(약 40% 큰) 입력을 같은 시간에 처리할 수 있다.

실제 현실적인 비용은 다항 중에서도 $O(n)$, $O(n^2)$, $O(n^3)$까지 정도다. 그 이상의 차수는 아무리 다항이라도 실제 쓰기에는 너무 비싸다.

그러나 일단 다항에 들어오는 알고리즘을 찾으면(예를 들어 $O(n^{100})$) 그 문제는 대개 몇 년 지나면 $O(n^3)$이나 $O(n^4)$ 정도의 알고리즘을 가지게 되고, 계속 연구되면서 꾸준히 줄어든다. $O(n^{2.5})$, $O(n^{1.5})$ 등. 일단 다항 알고리즘을 가지게 된 문제는, 필요하다면 더 빠른(다항 차수가 적은) 알고리즘이 늘 꾸준히 찾아져 왔다.

이런 문제를 ‘$P$ 클래스’ 문제라고 한다. 현실적인 비용으로 풀 수 있는 문제들. 다항(polynomial) 알고리즘이 있는 문제들을 말한다.

비현실적

쓸 수 없는 알고리즘. 그 비용이 너무 크면 비현실적인 알고리즘이다. 알고리즘의 실행비용(복잡도)이 상수 위에 지수로 입력 크기가 올라 가면($k^n$) 비현실적이라고 본다. 예를 들어 $O(2^n)$ 혹은 $O({2.5}^n)$이나 $O(100^n)$이다. 입력 크기가 커지면 기하급수적으로 증가하는 비용(exponential complexity)이다.

이런 비용은 무시무시하다. 예를 들어 $O(2^n)$인 경우 입력 크기($n$)가 100 정도만 되어도 $2^{100}$초다. 이 시간은 우주의 나이(${10}^{17}$~${10}^{18}$초)보다 길다. 우리 주변에 100개 이상 되는 입력 데이터를 다뤄야 하는 문제가 부지기수인 걸 생각하면 절망적이다.

$O(2^n)$의 경우 컴퓨터 성능이 2배 좋아져도 같은 시간에 풀 수 있는 입력의 크기는 고작 1만큼만 늘릴 수 있을 뿐이다. 절망적이다.

달이 지는 것을 막아줄 / 새벽녘에 뜨는 해를 수평선 밑으로 / 끌어내려 줄 사람이 아무도 없는가 / 그러면 나는 하루 더 살 수 있을 텐데 - 작자 미상, ‘심청전’ 중에서

기하급수로 증가하는 비용($O(k)$, 상수 $k>1$)의 알고리즘은, 곱하기로 빨라져도($pו$) 더하기만큼만 더 이득을 본다($p’+•$). 컴퓨터 성능이 $p$배 빨라지면 같은 입력에 대해서 비용이 $k^n/p$로 준다. 따라서 예전과 같은 시간에 처리할 수 있는 입력의 크기(즉 $k^n=k^m/p$인 $m$은)는 $n+\log_k{p}$만큼만 늘어날 뿐이다.

사실 다항과 기하급수의 분류는 실용적이지는 않다. 실제 쓸만한 알고리즘들은 다항에만 있고 그것도 차수가 아주 낮은 것들이기 때문이다. 프로그램으로 일을 봐야 하는 실제 사용자의 입장에서 다항이냐 기하급수냐는 관심 밖이다. 실용에서 중요한 건 다항 중에서도 차수가 낮냐 높냐다.

다항과 기하급수로 나누는 용도는 다른 데 있다. 컴퓨터로 풀기 어려운 문제가 과연 뭔지를 이해하려는 데 이 분류가 동원된다. 다항과 기하급수는 급이 다른 수준이 아니고 아예 종류가 다르다. 어떤 문제가 기하급수에 남아 있는 것과 다항으로 넘어오는 것. 문제의 근본적인 어려움이 있느냐 아니냐의 기준 같은 것이다.

P의 경계

컴퓨터로 풀기 어려운 문제란 무엇인가? 문제가 쉽고 어렵고의 경계는 어디인가?

그 경계를 정확히 그을 수 있으면 좋다. 분류가 되야 문제를 효과적으로 공략할 수 있기 때문이다. 무슨 병인지가 밝혀져야 치료법이 나온다. 문제가 나타나면, “그것은 어려운 문제다”라고 확인해 주면 있지도 않은 빠르고 정확한 알고리즘을 찾아 헤매는 낭비를 피할 수 있기 때문이다.

쉬운 문제는 정의하기 쉽고 판단하기도 쉽다. 현실적인 비용, 다항비용(polynomial complexity)의 알고리즘을 가지고 있으면 쉬운 문제다. 문제가 나타났다. 다항 알고리즘을 찾아 나선다. 찾아졌다. 쉬운 문제다.

난감한 건 어려운 문제다. 정의는 쉽지만 판단이 어렵다. 어려운 문제가 뭔가? 쉬운 문제가 아닌 문제다. 다항 알고리즘이 없는 문제다. 정의까지는 좋다. 문제는 판단하기다. 다항 알고리즘이 없다는 것을 어떻게 판단하는가? 문제가 나타났다. 다항 알고리즘을 찾아 나선다. 아무리 찾아도 없다. 더 찾으면 나타날까? 아니면 아예 없는 걸까? 아예 없다면 어려운 문제인 건데, 있을지 없을지를 모르겠으니 어려운 문제인지 쉬운 문제인지를 모르는 거다. 다항 풀이법을 아직 찾지 못한 문제, 아예 없는 문제일까?

어려운 문제의 경계를 판단하는 방법을 찾으려면 그 경계 가까이 가야 한다. 그 경계 가까이 있는 문제들의 집합이 있다.

모든 경계에는 꽃이 핀다. - 함민복, ‘꽃’

NP 클래스

‘$NP$ 클래스’ 문제라고 한다. 운에 기대면 현실적인 비용으로 해결할 수 있는(non-deterministic polynomial) 문제들.

이 $NP$ 클래스에는 우리가 관심 있는, 경계 가까이의 문제들이 들어온다. 현실적인 비용으로 풀 수 있는지는 아직 모르는 문제들. 알려진 알고리즘이라고는 기하급수의 비용을 가진 것밖에는 없는 문제들. 그러나 운에 기대면 현실적인 비용으로 해결할 수 있는 문제들.

운에 기대면 현실적인 비용으로 풀 수 있다는 것이 무슨 뜻인가? 미로찾기 문제를 생각하자. 입구에서 출구로 이어지는 길을 찾아야 한다. 갈림길마다 선택을 한다. 잘못 선택하면 시간을 낭비한다. 답이 아닌 곳으로 빠져 한참을 헤매게 된다. 하지만 선택의 갈림길마다 운 좋게 늘 옳은 답을 선택했다면? 시간 낭비 없이 출구를 찾게 된다. 그런 운 좋은 선택을 현실적인 횟수만큼 해서 탈출로를 찾을 수 잇다면 ‘운에 기대면 현실적인 비용으로 해결할 수 있는’ 문제인 거다.

정확히는 이렇다. $NP$ 문제인지 아닌지는 예/아니오를 묻는 문제 중에서 “예”(탈출로가 있다)라고 답하기까지만 따진다. 즉 “예”라는 답을 운에 기대어 현실적인 비용으로 할 수 있으면 $NP$ 문제라고 한다. 때문에 $NP$ 문제는 종종 이렇게 설명하기도 한다. “예”라고 답한 근거(찾은 탈출로)를 받아서 그 근거가 맞는지를 현실적인 비용에 확인할 수 있는 문제를 $NP$ 문제라고 한다. 답안을 받아서 그 답이 맞는지를 현실적인 비용으로 확인할 수 있는 문제라는 설명에 수긍이 가는 게, ‘답을 받아서’라는 것이 ‘운 좋으면…‘이라는 조건과 같은 말이지 않던가. 운은 답을 공짜로 알려주므로.

정의를 곱씹으면 $NP$ 클래스는 $P$ 문제를 모두 포함한다. 운에 기대지 않고도 쉽게 풀 수 있는 문제는 운에 기대도 쉽게 풀리는 문제다. 비행기를 타지 않아도 1시간 안에 갈 수 있는 거리는 비행기로도 1시간 안에 갈 수 있다.

그러나 위에 이야기한 대로 우리가 관심 있는 $NP$ 클래스 문제들은 $P$ 문제가 아니고 $P$와의 경계 가까이에 있는 문제다. 아직 찾지는 못했지만 현실적인 알고리즘이 있을 것만 같은 문제들.

다음과 같은 문제가 그런 $NP$ 문제들이다. 흥미롭게도 우리 주변에 흔히 나타나는 문제들이다.

  • 주어진 지도 위의 모든 도시를 한 번씩만 방문하는 경로가 있을까? 해밀턴 경로(hamiltonan path)라고 한다.

    지도에는 도시와 도시를 잇는 선이 있다. 도로가 있다는 뜻이다. 출발도시를 선택한다. 다음 도시는 그 도시와 도로로 연결된 도시다. 여러 이웃 도시 중에 하나를 선택해서 이동한다. 모든 도시를 한번씩 방문하는 경로가 있는 지도라면, 이런 선택이 모두 운 좋게 되면 그런 경로를 찾을 수 있다. 그런 운 좋은 선택은 도시의 개수만큼 하면 된다. $NP$ 문제인 게 확실하다.

    운에 기대지 않는 단순한 알고리즘은 모든 경우를 살펴보는 것이다. 방문할 도시의 수가 $n$개라고 하자. 모두 한 번씩 방문하는 경로의 후보들은 최대 $n!$만큼이다($n$개 도시들을 일렬로 순서 매기는 가짓수). 이 후보 경로 하나하나마다 주어진 지도에서 가능한지 살펴야 한다.

    다항시간에 푸는 알고리즘은 아직 없다.

  • 주어진 예산으로 주어진 지도의 도시들을 다 방문하고 돌아올 수 있을까?

    도시와 도시를 연결하는 도로가 있고, 도로의 길이만큼 비용이 든다고 하자. 주어진 입력은 도시들 사이의 도로망, 출발 도시, 그리고 주어진 예산이다. 출발한 시에서 다음 시를 선택하는 것이 필요하다. 어떻게? 섣불리 선택했다간 주어진 예산 내에 모든 도시를 방문하고 돌아오는 게 불가능할 수 있다. 돌아오는 방법이 있다면, 운 좋으면 그런 행선로를 찾을 수 있다. 몇 번 운이 좋아야 할까? 현재 도시에서 다음 도시를 선택하는 횟수만큼이다. 운에 기대어 선택해야 하는 횟수는 방문할 도시의 개수만큼이다. $NP$ 문제인 게 확실하다.

    운에 기대지 않는 단순한 알고리즘은 모든 경우를 살펴보는 것이다. 방문할 도시의 수 $n$개가 있고, 모든 두 도시 사이에 직통 도로가 뚫려 있다고 하면 방문하는 경로는 최대 $n!$개다. 이 모든 가능한 경로마다 비용을 보고 주어진 예산 안에 있는 것이 있으면 된다.

    다항시간에 푸는 알고리즘은 아직 없다.

  • 주어진 부울식(boolean formula)이 참이 되게 할 수 있을까?

    여기서 부울식은 변수와 그리고(and), 또는(or), 아닌(not)으로 조립된 식을 말한다. 예를 들어 $x((-x)+(-y))$. 이 식을 참으로 만드는 경우는 $x$가 1, $y$가 0이다. 어떤 식은 참으로 만들 방법이 없는 경우가 있다. 예를 들어 $x(-x)$가 참이 되는 경우는 없다.

    주어진 부울식이 $n$개의 변수로 구성돼 있으면 $n$개의 변수마다 0 또는 1이 가능하다. 변수마다 0과 1 중 하나를 선택해서 부울식의 참 거짓을 확인할 수 있다. 참일 수 있는 식이라면, 그 선택이 운 좋으면 참으로 만드는 경우를 찾게 될 것이다. $n$번 운 좋으면 참을 만드는 경우를 만들 수 있다. $NP$ 문제인 게 확실하다.

    운에 기대지 않고 하는 단순한 알고리즘은 모든 경우를 살펴보는 것이다. 변수 $n$개가 0 또는 1을 가지는 경우의 수는 $2^n$개다. 각 경우마다 주어진 부울식이 참인지 거짓인지를 본다. 최대 $2^n$만큼 살펴봐야 한다.

    이 문제도 다항시간에 푸는 알고리즘은 아직 없다.

  • 주어진 자연수를 인수분해하라.

    $n$자리 10진수를 받는다. 1도 아니고 자신도 아니면서 그 수를 나누는 수가 있을까? 있다면, $n$번만 운 좋으면 그 인자를 찾을 수 있다. 자릿수마다 0~9 중에서 운 좋게 선택하면 그 인수가 될 수 있다. $NP$ 문제인 게 확실하다.

    운에 기대지 않고 하는 단순 알고리즘은 모든 경우를 살펴보는 것이다. 받은 자연수보다 작은 수를 2부터 시작해서 하나하나 체크해 본다. 그 수로 원래 수가 나눠지는지, $n$자리 10진수이므로 최대 $10^n-1$만큼 반복해야 한다.

    디지털 컴퓨터에서 자연수를 소인수분해하는 다항 알고리즘은 아직 없다.

  • 아미노산이 연결된 1차원의 아미노산 실을 접어서 3차원의 단백질 구조물을 만들라. 단, 만들어진 구조를 유지하는 데 필요한 에너지가 어느 이하가 되는 3차원 생김새를 찾아야 한다.

    단백질 접기(protein folding)라고 하는데, 모든 생명체가 자신의 모습을 만들 때 하는 일이다.

    기차 같이 연결된 아미노산들을 처음부터 차례로 올바른 방향으로 접어간다면 에너지 예산 안에서 지탱할 수 있는 구조물을 만들게 될 것이다. 운 좋으면 항상 올바른 방향으로 접을 수 있다. 아미노산 실의 길이만큼(아미노산의 개수 $n$만큼) 잘 접으면 된다. $NP$ 문제다.

    운에 기대지 않고 하는 단순한 알고리즘은 각 아미노산마다 3차원에서 접을 수 있는 방향이 6개라면 $6^n$개의 경우를 모두 시도해서 그중 에너지 조건을 만족하는 구조물인지 봐야 한다.

  • 1000만 관객을 넘기는 영화를 제작하라. 제작 중 $n$번 디자인 선택을 해야 한다.

    매번 디자인 선택(시나리오 작성, 배우 캐스팅, 촬영, 편집 등) 때마다 올바른 방향으로 해 간다면 만들 수 있다. 운 좋으면 매번($n$번) 그런 방향으로 디자인 선택을 하게 된다. $NP$ 문제다.

    운에 기대지 않고는 비현실적인 비용이 든다. 매번 마주치는 디자인 후보의 가짓수가 2개뿐이라 해도, 만들 수 있는 영화의 가짓수는 $2^n$개다. 이것들을 모두 살피면서 1000만 관객을 넘길 것을 선택해야 한다.

이런 $NP$ 문제들은 곤혹스럽다. 주변에 흔히 만나는 문제들인데 현실적인 비용으로 정확히 답을 내는 알고리즘은 아직 없다니.

내가 살아 있는 동안 / 다 마칠 수 있는 일이란 / 이 세상에 없다 / 진정코 이 세상이란 몇천 년이나 걸려야 / 집 한 채 지을 수 있음이여 - 고은, ‘몇 천 년’

그런데 아이러니하게도 이런 $NP$ 문제들은 그렇기 때문에 유용하기도 하다. 디지털 암호기술을 버티는 기둥이 이런 $NP$ 문제들이기 때문이다. 메시지의 보안을 위해 이런 $NP$ 문제들이 쓰인다. 암호화된 메시지를 도청한 사람이 암호를 풀려면 이런 $NP$ 문제를 풀어야 하게끔 만들어 놓는 것이다. 알고리즘 복잡도(algorithm complexity)의 초석을 놓은 미뉴엘 블럼(Manuel Blum)이 시작한 역발상이다. 과학이 기술로 응용되는 지점에서 종종 목격되는 역발상의 지혜다.

아무튼, 어려운 문제가 뭔지를 판단하는 방법을 컴퓨터과학에서 어떻게 찾아가는지, 그 이야기를 계속하자. $NP$ 클래스라는 문제들로 어려운 문제와 쉬운 문제의 경계를 더듬고 있다.

오리무중

$P$가 $NP$가 같은지 아닌지는 아직 누구도 답하지 못했다.

정말 어려운 문제인지가 아리송한 경계의 문제들($NP$), 이것들이 혹시 쉬운 문제($P$)는 아닐까? $NP$ 문제는 운에 기대면 현실적인 비용으로 풀 수 있는(non-deterministic polynomial) 문제들이다. 이런 문제들을 운에 기대지 않고도 현실적인 비용으로 풀 수 있을까? 현재의 디지털 컴퓨터(튜링기계)로는 아마도 \(P\neq NP\) 일 거라고 많은 전문가들은 추측하고 있다.

$P≠NP$라면, “어려운 문제의 경계가 어디냐”라는 질문에 조금 구체적으로 답할 수 있게 된다. 운에 기대지 않고는 비용이 너무 드는 문제들, 그런 문제들이 존재하는 게 확실해졌고, 여기부터가 어려운 문제들의 시작이라고 정의해도 되는 게 아닐까. 운에 기대기만 하면 $P$로 넘어가는 아슬아슬한 경계에 있으므로 $P$는 될 수 없으므로.

만일 $P=NP$라고 판명되면, 이 세계의 많은 것이 기계적인 세상으로 위축되는 느낌이다. $P=NP$인 세상은 비유하자면 “명작을 현실적인 비용으로 컴퓨터가 자동 생산할 수 있다”가 된다. 명작 만들기는 선택의 연속이었을 것이다. 각 선택마다 수없이 많은 디자인 후보 중에서 명작으로 가는 하나를 절묘하게 선택해가면서 엮어 놓은 것 아닐까. 이 선택을 자동화해서 현실적인 비용에 늘 그런 명작이 자동으로 만들어지도록 할 수 있을까? 만든 음악을 듣고 좋다 나쁘다 판단하기는 쉽지만, 그런 음악을 만드는 것은 그보다는 차원이 다르게 어려운 문제가 아닐까. 좋은 그림, 좋은 소설, 좋은 시 등이 마찬가지 아니던가. 수많은 디자인 선택을 절묘하게 구성한 결과들일 것이다. $P=NP$면 그런 명작도 자동으로 쉽게 만들 수 있다는 이야기다. 뭔가 아니라는 느낌 아닌가.

$P≠NP$? 튜링상(Turing Award)은 그 답을 기다린다. 컴퓨터과학의 노벨상이다.

P의 바깥

$P≠NP$가 사실이라면, $P$ 말고 $NP$에만 있는 문제들이 있는 것이다. 그런 문제는 $P$가 아니므로 어려운 문제다. 그렇다면 $P≠NP$라는 전제 아래 어떤 문제가 $P$ 바깥에 있는지(어려운 문제인지) 판단하는 방법이 있을까?

한 방법이 겨우 있다. ‘겨우’라는 표현을 쓴 것은, 그 방법이 믿을 만하지만 완전하지는 않기 때문이다. 모든 $P$ 바깥의 문제가 그 방법으로 확인되는 건 아니다. 그 방법을 통과하면 $P$ 바깥의 문제인 것은 확실하지만(믿을 만한 방법이지만), 어떤 문제는 $P$ 바깥의 문제인데도 그 방법을 통과하지 못할 수 있다(완전하지는 않다).

그 방법은 $NP$ 클래스의 재미있는 성질 때문에 가능하다. 그 성질은, $NP$ 문제 중에서도 가장 어려운 문제가 있다는 것이다. $NP$ 클래스의 나머지 문제들은 모두 이 문제보다 쉽다. 따라서 $P≠NP$라면 이 문제는 당연히 $P$ 바깥이다. 내가 만난 문제가 그 문제만큼 어렵다면 그 문제는 분명 $P$ 바깥인 거다. 이 사실을 발견하는 데는 건너풀기(problem reduction)라는 개념이 지렛대로 사용된다.

건너풀기

강 건너가 건너온다. / 누가 끌배를 끌고 있다. - 이문제, ‘독거’

$NP$로 분류한 문제들은 모두 단단히 연대해 있다. 달라 보이지만 사실은 한 덩어리로 움직이는 하나다. 어려운 문제 쪽에 있지만 쉬운 문제 진영에 바싹 다가서 있는 문제들. 이것들은 하나만 넘어오면 모두 한 몸 같이 넘어오는 성질을 가지고 있다.

이런 연대가 건너풀기(reducibility, problem reduction)라는 개념으로 확인된다. ‘건너풀기’란 건너에서 간접적으로 풀기다. 문제 $A$를 풀어야 한다. 건너편 문제 $B$를 풀고 그 알고리즘을 이용해서 $A$를 풀 수 있다. $A$ 문제를 푸는 데 $B$ 문제로 건너 푼 것이다. 간접적으로 푼 것이다. 단, $B$ 문제로 푼 답을 $A$ 문제의 답으로 옮기는 건 쉬워야(다항 비용으로 되야) 한다.

건너풀기는 일상에서 흔하다. 곱하기를 더하기로 건너 풀 수 있다. 더하기만 할 줄 알면 곱하기는 해결되기 때문이다. $5×12$는 5를 12번 더하면 된다. 이게 건너풀기다.

건너풀기로 $NP$ 문제들을 모두 지배하는 하나의 문제가 있다. $NP$ 클래스의 문제 중에 ‘종결자’ 역할을 하는 대표적인 문제가 있는 것이다. 이 문제만 현실적인 비용으로 풀리면, 나머지 $NP$ 문제들은 모두 이 문제로 건너 풀 수 있다. 모든 $NP$ 문제를 종결자 문제로 바꿔서 낼 수 있고, 종결자 문제의 알고리즘으로 풀어서 그 답을 원 문제의 답으로 바꾸면 된다. 종결자 문제의 알고리즘이 다항 알고리즘이면(그리고 문제를 바꾸고 답을 바꾸는 비용이 다항이면), 모든 $NP$ 문제도 다항 알고리즘으로 풀리는 것이다. 이 종결자 문제로 건너 풀면서 모든 $NP$ 문제들이 다 현실적인 비용으로 풀리는 것이다.

$NP$에 있는 이런 종결자 문제를 $NP$-완전(complete) 문제라고 한다. 모든 $NP$ 문제들이 빠짐없이 이 문제를 통해 건너 풀리기 때문이다. ‘완전’이라는 훈장을 달아준 이유다. $NP$ 문제들 중에서 가장 어려운 문제라고 볼 수 있다.

  • 컴퓨터과학에서 complete라는 단어가 종종 나온다. ‘빠뜨림이 없다’는 뜻이다. 그래서 ‘완전’이다. 참고로, 종결자 역할은 하지만 $NP$ 문제인지 확인되지 않은 문제는 $NP$-하드(hard) 문제라고 한다.

$NP$ 문제들의 종결자 문제를 처음으로 발견한 사람은 스테픈 쿡(Stephen Cook)이다. 그는 건너풀기 개념을 가지고 $NP$에 있는 하나의 문제를 찾아서 그 문제가 $NP$의 모든 문제보다 어렵다는 놀라운 사실을 증명했다. 1971년이었고, 이 업적으로 1982년 튜링상을 받는다. 쿡이 찾은 최초의 $NP$-완전 문제는 주어진 부울식이 참일 수 있는지 결정하는 문제였다. 쿡에 이어 1972년, 스물한 개의 다양한 $NP$-완전 문제들이 발견된다. 특히 우리가 일상에서 마주치는 흔한 문제들이 $NP$-완전 문제들이었다. 예를 들어 모든 도시를 한 번씩 방문하는 경로(해밀턴 경로) 찾기 문제 등이다. 리처드 카프(Richard Karp)가 이를 증명했고 그 업적으로 튜링상을 받는다(1985년). 현재 우리가 일고 있는 $NP$-완전 문제는 수천가지다.

어려운 문제인지 판단하기

이제 이 $NP$-완전 문제가 어려운 문제를 판별하는 기준으로 쓰일 수 있다. 문제가 ‘어렵다’는 정의는 ‘$P$가 아니다’라는 것이다. $P≠NP$가 사실이라면(그럴 것이라고 추측하고 있다), 주어진 문제가 적어도 $NP$-완전 문제만큼 어렵기만 하면 그 문제는 $P$ 바깥의 문제다.

그래서 어떤 문제를 만났을 때, 다항 알고리즘을 찾지 못하고 있다면 $NP$-완전인지, 혹은 알려진 $NP$-완전 문제를 그 문제로 건너풀 수 있는지를 확인한다. 사실이라면 적어도 $NP$-완전 문제만큼 어려운 문제다. 그러면 그 문제는, $P≠NP$가 사실이라면 $P$ 바깥의 문제임이 확실하다.

이게 컴퓨터과학이 찾아낸 어려운 문제 판별법이다. $P≠NP$인지를 아직 확인 못해서 아쉽지만, 지금까지 이게 최선이다.

Comment  Read more

최대 유량 알고리즘 (1)

|

이번 글에서는 최대 유량 알고리즘(Max Flow Algorithm)을 포드-풀커슨 알고리즘(Ford-Fulkerson Algorithm)을 중심으로 살펴보도록 하겠습니다. 이 글은 고려대 김선욱 교수님과 역시 같은 대학의 김황남 교수님 강의와 위키피디아를 정리했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

concept

최대 유량 알고리즘이란 가중치가 있는 방향그래프(directed graph) $G$와 시작(source) 노드 $s$, 도착(sink) 노드 $t$가 주어졌을 때 각 엣지의 용량(capacity)을 고려하여 $s$에서 $t$로 흘려보낼 수 있는 최대 유량(flow)을 구하는 알고리즘을 가리킵니다. 전력, 수도, 통신 등 다양한 분야에서 폭넓게 쓰이고 있습니다. 용어 및 개념 몇 가지 살펴보겠습니다.

$G$의 각 엣지 가중치를 용량(capacity)라고 하며 ($u,v$)의 용량을 $c(u,v)$라고 씁니다. 용량은 항상 0 이상입니다. 노드 $u$와 $v$ 사이를 흐르는 유량(net flow)은 $f(u,v)$라고 쓰며 유량은 실수치 함수(real-valued function)입니다. 최대 유량 문제에는 다음 세 가지 제약조건이 있습니다. 곱씹어보면 상식적인 것들입니다.

  • capacity constraint : $f(u,v)≤c(u,v)$, 즉 유량은 용량을 초과할 수 없다.
  • skew symmetry : $f(u,v)=-f(v,u)$
  • flow conservation : 각 노드에서 유량은 새로 더해지거나 감소되지 않는다.

이번엔 그림 표기 방법을 살펴보겠습니다. 용량 정보가 $c(u,v)=10$이고 $c(v,u)=4$라면 다음과 같이 표시할 수 있습니다.

$u$에서 $v$로의 유량이 8이라면 다음과 같이 표시합니다.

$v$에서 $u$로의 유량이 3이라면 다음과 같이 표시합니다.

위 그래프를 양의 순 유량(positive net flow)으로 다시 표시하면 다음과 같습니다. 위 그래프를 아래처럼 바꾸는 과정을 cancellation이라고 합니다.

이번엔 residual network에 대해 살펴보겠습니다. $u$에서 $v$로의 residual capacity $c_f(u,v)$는 다음과 같이 정의됩니다.

[{ c }_{ f }\left( u,v \right) =c\left( u,v \right) -f\left( u,v \right)]

예를 들어보겠습니다. 하단 좌측과 같은 유량 그래프 $G$가 주어졌을 때 $G$의 residual network $G_f$는 하단 우측과 같습니다. $G_f$의 엣지는 cancallation 과정을 거쳐 도출된 양의 순 유량을 바탕으로 계산됩니다.

  • $c_f(u,v)=c(u,v)-f(u,v)=9-4=5$
  • $c_f(v,u)=c(v,u)-f(v,u)=0-(-4)=4$

augmenting pathresidual network $G_f$ 상에서 $s$에서 $t$로 가는 경로를 가리킵니다.

Ford-Fulkerson Algorithm

포드-풀커슨 알고리즘(Ford-Fulkerson Algorithm)은 residual network를 활용해 최대 유량을 구하는 기법입니다. 다음 그림과 같은 residual network $G_f$가 주어졌다고 칩시다.

우선 깊이우선탐색(DFS)이나 너비우선탐색(BFS) 방식으로 $s$에서 $t$로 가는 경로를 하나 찾습니다. 아래 예시에서는 {$s,a,b,t$}를 찾았습니다. 이 경로를 통해 흐를 수 있는 최대 유량은 1입니다.

직전 단계에서 구한 경로의 반대 방향으로 최대 유량(1)만큼을 흘려 보내는 엣지를 만들어 줍니다(augmenting path+cancellation). 다시 $s$에서 $t$로 가는 경로를 하나 찾습니다. {$s,a,t$}입니다. 이 경로를 통해 흐를 수 있는 최대 유량은 1입니다.

직전 단계에서 구한 경로의 반대 방향으로 최대 유량(1)만큼을 흘려보내는 엣지를 만들어 줍니다. 다시 $s$에서 $t$로 가는 경로를 하나 찾습니다. {$s,b,a,t$}입니다. 이 경로를 통해 흐를 수 있는 최대 유량은 1입니다.

직전 단계에서 구한 경로의 반대 방향으로 최대 유량(1)만큼을 흘려보내는 엣지를 만들어 줍니다. 다시 $s$에서 $t$로 가는 경로를 하나 찾습니다. {$s,b,t$}입니다. 이 경로를 통해 흐를 수 있는 최대 유량은 2입니다.

직전 단계에서 구한 경로의 반대 방향으로 최대 유량(2)만큼을 흘려보내는 엣지를 만들어 줍니다. 다시 $s$에서 $t$로 가는 경로를 하나 찾습니다. 그런데 이제는 $s$에서 $t$로 가는 경로가 존재하지 않습니다.

포드-풀커슨 알고리즘 수행을 종료합니다. 위 그래프는 residual network $G_f$이기 때문에, $s$에서 $t$로 향하는 최대 유량은 $a$에서 $t$의 2, $b$에서 $t$의 3, 이렇게 해서 총 5가 됩니다. 포드-풀커슨 알고리즘의 의사코드는 다음과 같습니다.

이번엔 다른 예시를 보겠습니다. 다음과 같은 유량그래프 $G$가 주어졌다고 칩시다.

위 유량그래프를 기반으로 만든 residual network $G_f$는 다음과 같습니다. $G_f$에서 $s$에서 $t$로 향하는 경로(augmenting path)를 찾습니다(BFS, DFS 등 활용). 붉은색으로 표시해 두었습니다.

이 경로를 통해 흐를 수 있는 최대 유량은 4라는 것을 확인할 수 있습니다. 경로의 반대 방향으로 최대 유량(4)만큼을 흘려보내는 엣지를 만들어 줍니다. 다음과 같습니다.

residual network $G_f$를 바탕으로 유량그래프 $G$를 만들면 다음과 같습니다. 아래의 유량그래프는 처음 분석을 시작한 그래프와 동치입니다.

포드-풀커슨 알고리즘은 residual network $G_f$ 상의 $s$에서 $t$로 가는 경로가 존재하지 않을 때까지 augmenting pathcancellation을 반복적으로 수행해 줍니다.

Comment  Read more

최소 신장 트리

|

이번 글에서는 최소신장트리(Minimum Spanning Tree, MST)를 살펴보도록 하겠습니다. 이 글은 고려대 김선욱 교수님과 역시 같은 대학의 김황남 교수님 강의와 위키피디아를 정리했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

concept

신장트리(Spanning Tree)란 (1) 원 그래프의 모든 노드를 포함하고 (2) 모든 노드가 서로 연결되어 있으면서 (3) 트리(Tree)의 속성을 만족하는 그래프를 가리킵니다. 예컨대 하단 좌측과 같은 원 그래프의 스패닝트리는 하단 우측과 같이 총 8개의 스패닝트리들을 가질 수 있습니다.

최소신장트리(MST)는 가능한 신장트리 가운데 엣지 가중치의 합이 최소인 신장트리를 말합니다. 예컨대 하단 좌측과 같은 원 그래프의 MST는 하단 우측과 같습니다.

MST는 노드 간 연결성을 보장하면서 노드 사이를 잇는 거리/비용 등을 최소로 하는 그래프를 의미하기 때문에 응용 범위가 넓습니다. 이 글에서는 원 그래프에서 MST를 찾아내는 기법인 크루스칼 알고리즘(Kruskal’s algorithm)프림 알고리즘(Prim’s algorithm)을 살펴보도록 하겠습니다.

크루스칼 알고리즘

크루스칼 알고리즘은 분석 대상 노드에 연결된 엣지 가운데 가중치가 최소인 엣지를 고르되, 이렇게 추가된 엣지로 그래프가 트리 속성이 깨지지 않는지 여부를 체크하는 방식으로 작동합니다. 이때 가중치가 최소인 엣지를 light edge, 추가 엣지로도 트리 속성을 만족하고 있다면 해당 엣지를 safe하다고 합니다. 요컨대 크루스칼 알고리즘은 safe하고 light한 엣지를 반복적으로 찾아가는 기법입니다. 크루스칼 알고리즘의 작동 방식을 예시와 함께 보도록 하겠습니다.

크루스칼 알고리즘은 디스조인트 셋(disjoint set)을 기본 자료구조로 활용합니다. (a)와 같이 주어진 그래프가 있다고 칩시다. 우선 모든 노드에 대해 make-set 연산을 수행합니다. 이후 모든 엣지를 가중치를 기준으로 오름차순 정렬합니다. 결과(MST)로 반환할 $A$는 공집합으로 초기화해 둡니다. 크루스칼 알고리즘은 가중치가 가장 작은 엣지부터 분석하기 때문에 시작노드를 별도로 설정하지 않습니다.

(a)를 보겠습니다. 그래프 모든 엣지 가운데 가중치가 최소(1)인 ($h,g$)가 분석 대상입니다. $h$와 $g$가 서로 다른 셋(find 연산으로 확인)이기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($h,g$)를 추가하고 $h$가 속한 셋과 $g$가 속한 셋을 합칩니다(Union 연산).

(b)를 봅시다. 남은 엣지 가운데 가중치가 최소(2)인 ($i,c$)가 분석 대상입니다. $i$와 $c$가 서로 다른 셋이기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($i,c$)를 추가하고 $i$가 속한 셋과 $c$가 속한 셋을 합칩니다.

(c)를 봅시다. 남은 엣지 가운데 가중치가 최소(2)인 ($g,f$)가 분석 대상입니다. $g$가 속한 셋과 $f$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($g,f$)를 추가하고 $g$가 속한 셋과 $f$가 속한 셋을 합칩니다.

(d)를 봅시다. 남은 엣지 가운데 가중치가 최소(4)인 ($a,b$)가 분석 대상입니다. $a$가 속한 셋과 $b$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($a,b$)를 추가하고 $a$가 속한 셋과 $b$가 속한 셋을 합칩니다.

(e)를 봅시다. 남은 엣지 가운데 가중치가 최소(4)인 ($c,f$)가 분석 대상입니다. $c$가 속한 셋과 $f$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($c,f$)를 추가하고 $c$가 속한 셋과 $f$가 속한 셋을 합칩니다.

(f)를 봅시다. 남은 엣지 가운데 가중치가 최소(6)인 ($i,g$)가 분석 대상입니다. $i$가 속한 셋과 $g$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 바꿔 말해 사이클(cycle)이 존재한다는 이야기입니다. 따라서 ($i,g$)는 건너 뜁니다.

(g)를 봅시다. 남은 엣지 가운데 가중치가 최소(7)인 ($c,d$)가 분석 대상입니다. $c$가 속한 셋과 $d$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($c,d$)를 추가하고 $c$가 속한 셋과 $d$가 속한 셋을 합칩니다.

(h)를 봅시다. 남은 엣지 가운데 가중치가 최소(7)인 ($i,h$)가 분석 대상입니다. $i$가 속한 셋과 $h$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 따라서 ($i,h$)는 건너 뜁니다.

(i)를 봅시다. 남은 엣지 가운데 가중치가 최소(8)인 ($a,h$)가 분석 대상입니다. $a$가 속한 셋과 $h$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($a,h$)를 추가하고 $a$가 속한 셋과 $h$가 속한 셋을 합칩니다.

(j)를 봅시다. 남은 엣지 가운데 가중치가 최소(8)인 ($b,c$)가 분석 대상입니다. $b$가 속한 셋과 $c$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 따라서 ($b,c$)는 건너 뜁니다.

(k)를 봅시다. 남은 엣지 가운데 가중치가 최소(9)인 ($d,e$)가 분석 대상입니다. $d$가 속한 셋과 $e$가 속한 셋이 서로 다르기 때문에 트리 속성을 만족(safe)합니다. 따라서 $A$에 분석 대상 엣지 ($d,e$)를 추가하고 $d$가 속한 셋과 $e$가 속한 셋을 합칩니다.

(l)을 봅시다. 남은 엣지 가운데 가중치가 최소(10)인 ($e,f$)가 분석 대상입니다. $e$가 속한 셋과 $f$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 따라서 ($e,f$)는 건너 뜁니다.

(m)을 봅시다. 남은 엣지 가운데 가중치가 최소(11)인 ($b,h$)가 분석 대상입니다. $b$가 속한 셋과 $h$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 따라서 ($b,h$)는 건너 뜁니다.

(n)을 봅시다. 남은 엣지 가운데 가중치가 최소(14)인 ($d,f$)가 분석 대상입니다. $d$가 속한 셋과 $f$가 속한 셋이 서로 같기 때문에 트리 속성을 만족하지 않습니다. 따라서 ($d,f$)는 건너 뜁니다.

모든 엣지에 대해 분석을 마쳤으므로 크루스칼 알고리즘 수행을 종료합니다. 크루스칼 알고리즘의 의사코드는 다음과 같습니다.

크루스칼 알고리즘의 계산복잡성을 따져보겠습니다. 모든 노드에 대해 Make-Set 연산을 수행하므로 초기화에 필요한 계산량은 $O(V)$입니다. 이후 엣지를 중심으로 정렬을 수행하므로 현존하는 알고리즘 가운데 가장 성능 좋은 기법을 쓸 경우 그 계산량은 $O(E\lg{E})$입니다.

이제 반복문을 보겠습니다. 셋에 속한 데이터 수가 $n$개일 때 Find-Set , Union 연산의 계산복잡성은 모두 $O(\lg{n})$이고, 이를 전체 엣지에 대해 반복 수행하므로 반복문에서 필요한 계산량은 $O(E\lg{E})$입니다. 따라서 크루스칼 알고리즘의 전체적인 계산복잡성은 $O(E\lg{E})$가 됩니다.

프림 알고리즘

프림 알고리즘은 solution setnon-solution set 사이를 연결하는 엣지 가운데 가중치가 가장 작은 엣지를 하나 뽑고 이 엣지에 연결된 노드와 해당 엣지를 solution set에 넣습니다. 아래 그림에서는 $v$와 ($u,v$)를 $S$에 집어넣는다고 보시면 됩니다.

이를 solution set에 모든 노드가 포함될 때까지 반복수행하면 프림 알고리즘이 종료됩니다. 프림 알고리즘을 예시와 함께 살펴보도록 하겠습니다.

(a)와 같은 그래프가 주어졌을 때 우선 자료구조를 초기화합니다. (a)를 먼저 보겠습니다. 노드 $a$에 연결된 엣지 가운데 가중치가 4로 가장 작은 엣지 ($a,b$)를 선택합니다. (b)와 같습니다.

(c)를 보겠습니다. (b)에서 선택한 엣지와 관계 있는 노드 {$a,b$}에 연결된 엣지들 가운데 가중치가 8로 가장 작은 ($b,c$)를 선택합니다.

(d)를 보겠습니다. (a)와 (b)에서 선택한 엣지와 관계 있는 노드 {$a,b,c$}에 연결된 엣지들 가운데 가중치가 2로 가장 작은 엣지 ($c,i$)를 선택합니다.

(e)를 보겠습니다. {$a,b,c,i$}에 연결된 엣지들 가운데 가중치가 4로 가장 작은 엣지 ($c,f$)를 선택합니다. (f)에서는 {$a,b,c,f,i$}에 연결된 엣지들 가운데 가중치가 2로 가장 작은 엣지 ($g,f$)를 선택합니다.

(g)에서는 {$a,b,c,f,g,i$}에 연결된 엣지들 가운데 가중치가 1로 가장 작은 엣지 ($g,h$)를 선택합니다. (h)에서는 {$a,b,c,f,g,h,i$}에 연결된 엣지들 가운데 가중치가 7로 가장 작은 엣지 ($c,d$)를 선택합니다. 여기서 주의할 점은 ($h,i$) 또한 그 가중치가 7로 연결되지 않은 엣지 가운데 가장 작은 것은 맞지만, 노드 $h$와 $i$ 모두 이미 선택된 노드들이므로 해당 엣지는 선택에서 배제된다는 것입니다.

(i)에서는 {$a,b,c,d,f,g,h,i$}에 연결된 엣지들 가운데 가중치가 9로 가장 작은 엣지 ($d,e$)를 선택합니다. 이로써 모든 노드가 MST로 선택됐으므로 프림 알고리즘 수행을 종료합니다.

프림 알고리즘의 의사코드는 다음과 같습니다.

프림 알고리즘의 계산복잡성을 따져보겠습니다. 여기서는 최대/최소값을 찾는 데 효율적인 힙(heap)을 쓴다고 가정해 보겠습니다. 위 의사코드에서 u.key란 노드 $u$에 연결된 엣지 가중치의 최소값이며 u.π란 노드 $u$의 부모노드를 가리킵니다.

우선 모든 노드를 초기화(non-solution set $Q$에 모든 노드 삽입 등)하는 데 $O($|$V$|$)$만큼의 계산복잡성이 듭니다. 모든 노드 가운데 key값이 가장 작은 걸 뽑아내는 extract-MIN 연산을 하려면 힙의 말단 노드가 루트 노드까지 자리 이동을 해야 하므로 트리의 높이에 상당하는 $O(lg{V})$만큼의 계산이 필요합니다.

decrease-KEY(Q, v, w(v,u)) 연산은 노드 $v$의 부모노드가 $u$가 되도록 하고, $v$의 key값(=$v$에 연결된 엣지 가중치의 최소값)을 ($u,v$)의 가중치로 업데이트하는 걸 가리킵니다. 힙의 키값을 업데이트하려면 최대 트리의 높이에 상당하는 탐색을 해야 하므로 $O(\lg{V})$만큼의 계산이 필요합니다. 이것을 노드 $u$에 연결된 엣지 수(그 기대값은 $E/V$)만큼 수행해야 하므로 decrease-KEY 연산은 $O(E/V\lg{V})$만큼의 계산이 필요합니다.

extract-MINdecrease-KEY 연산은 모든 노드에 대해 반복 수행(non-solution set $Q$가 공집합이 될 때까지)해야 하므로 프림 알고리즘의 전체적인 계산복잡성은 $O((V+E)\lg{V})$가 됩니다.

Comment  Read more

벨만-포드 알고리즘

|

이번 글에서는 최단 경로(Shortest Path)를 찾는 대표적인 기법 가운데 하나인 벨만-포드 알고리즘(Bellman-Ford’s algorithm)을 살펴보도록 하겠습니다. 이 글은 고려대 김선욱 교수님과 역시 같은 대학의 김황남 교수님 강의와 위키피디아를 정리했음을 먼저 밝힙니다. 그럼 시작하겠습니다.

concept

최단경로 문제의 optimal substructure를 확장하면 최단경로를 다음과 같이 분해(decompostion)할 수 있습니다. 시작노드 $s$에서 $v$에 이르는 최단경로는 $s$에서 $u$까지의 최단경로에 $u$에서 $v$ 사이의 가중치(거리)를 더한 값이라는 겁니다.

[D\left( s,v \right) =D\left( s,u \right) +w\left( u,v \right)]

벨만-포드 알고리즘은 $s,u$ 사이의 최단경로를 구할 때 그래프 내 모든 엣지에 대해 edge relaxation을 수행해 줍니다. 그러면 이를 몇 번 수행해야 할까요? 생각해 보면 $s,u$ 사이의 최단경로는 $s$와 $u$뿐일 수 있고, $u$를 제외한 그래프의 모든 노드(|$V$|$-1$개)가 $s,u$ 사이의 최단경로를 구성할 수도 있습니다. 따라서 벨만-포드 알고리즘은 모든 엣지에 대한 edge-relaxation을 |$V$|$-1$회 수행합니다.

수행예시1

하단 좌측 그림과 같은 그래프에 벨만-포드 알고리즘을 적용해 보겠습니다. 우선 시작노드 $A$를 제외한 모든 노드의 거리를 무한대로 초기화합니다. edge relaxation 순서는 order와 같습니다(예시를 위해 정해놓은 것일 뿐입니다).

우선 ($B,E$)를 보겠습니다. 무한대-2=무한대이므로 업데이트할 필요가 없습니다. ($C,E$), ($F,C$), ($D,F$), ($C,B$) 또한 마찬가지입니다. ($A,B$)의 경우 시작노드 $A$에서 $B$에 이르는 거리가 8이고, 이는 기존 거리(무한대)보다 작으므로 8을 $B$에 기록해 둡니다. 마찬가지로 $C,D$도 각각 -2, 4로 기록해 둡니다. 이로써 그래프 모든 엣지에 대한 첫번째 edge relaxation이 끝났습니다.

상단 좌측 그림 차례입니다. ($B,E$)의 경우 8-2=6이고 이는 기존 거리(무한대)보다 작으므로 6을 $E$에 기록해 둡니다. ($C,E$)의 경우 -2+3=1이고 이는 기존 거리(6)보다 작으므로 1을 $E$에 기록해 둡니다. ($F,C$)의 경우 무한대+9=무한대이므로 업데이트할 필요가 없습니다. ($D,F$)의 경우 4+5=9이고 이는 기존 거리(무한대)보다 작으므로 9를 $F$에 기록해 둡니다. ($C,B$)의 경우 -2+7=5이고 이는 기존 거리(8)보다 작으므로 5를 $B$에 기록해 둡니다. ($C,D$)의 경우 -2+1=-1이고 이는 기존 거리(4)보다 작으므로 -1을 $D$에 기록해 둡니다.

($A,B$)의 경우 0+8=8이고 이는 기존 거리(5)보다 크므로 업데이트할 필요가 없습니다. ($A,C$)의 경우 0-2=-2이고 이는 기존 거리(-2)와 같으므로 업데이트할 필요가 없습니다. ($A,D$)의 경우 0+4=4이고 이는 기존 거리(-1)보다 크므로 업데이트할 필요가 없습니다. 이로써 그래프 모든 엣지에 대한 두번째 edge relaxation이 끝났습니다.

이번엔 상단 우측 그림 차례입니다. ($B,E$)의 경우 5-2=3이고 이는 기존 거리(1)보다 크므로 업데이트할 필요가 없습니다. 이는 ($C,E$), ($F,C$) 또한 마찬가지입니다. ($D,F$)의 경우 -1+5=4이고 이는 기존 거리(9)보다 작으므로 4를 $F$에 기록해 둡니다. ($C,B$), ($C,D$), ($A,B$), ($A,C$), ($A,D$)는 모두 기존 거리보다 크므로 업데이트할 필요가 없습니다. 이로써 그래프 모든 엣지에 대한 세번째 edge relaxation이 끝났습니다.

벨만-포드 알고리즘은 시작노드를 제외한 전체 노드수 만큼의 edge relaxation을 수행해야 합니다. 위 예시의 경우 총 5회 반복 수행해야 합니다. 그런데 네번째 edge relaxation부터는 거리 정보가 바뀌지 않으므로 생략했습니다.

수행예시2

벨만-포드 알고리즘의 또다른 수행 예시입니다. 그래프 모든 엣지에 대한 edge relaxation을 1회 수행한 것입니다. edge relaxtion을 수행할 때 거리정보뿐 아니라 최단경로(음영 표시) 또한 업데이트한다는 걸 알 수 있습니다.

negative cycle

다익스트라 알고리즘(Dijkstra’s algorithm)과 달리 벨만-포드 알고리즘은 가중치가 음수인 경우에도 적용 가능합니다. 그러나 다음과 같이 음수 가중치가 사이클(cycle)을 이루고 있는 경우에는 작동하지 않습니다.

위 그림에서 $c,d$ 그리고 $e,f$가 사이클을 이루고 있는 걸 확인할 수 있습니다. $c,d$의 경우 사이클을 돌 수록 거리가 커져서 최단경로를 구할 때 문제가 되지 않습니다. 반면 $e,f$의 경우 사이클을 돌면 돌수록 그 거리가 작아져 벨만-포드 알고리즘으로 최단경로를 구하는 것 자체가 의미가 없어집니다.

따라서 그래프 모든 엣지에 대해 edge relaxation을 시작노드를 제외한 전체 노드수 만큼 반복 수행한 뒤, 마지막으로 그래프 모든 엣지에 대해 edge relaxation을 1번 수행해 줍니다. 이때 한번이라도 업데이트가 일어난다면 위와 같은 negative cycle이 존재한다는 뜻이 되어서 결과를 구할 수 없다는 의미의 false를 반환하고 함수를 종료하게 됩니다. 벨만-포드 알고리즘 전체의 의사코드는 다음과 같습니다.

계산복잡성

벨만-포드 알고리즘은 그래프 모든 엣지에 대해 edge relaxation을 시작노드를 제외한 전체 노드수 만큼 반복 수행하고, 마지막으로 그래프 모든 엣지에 대해 edge relaxation을 1번 수행해 주므로, 그 계산복잡성은 $O($|$V$||$E$|$)$이 됩니다. 그런데 dense graph는 엣지 수가 대개 노드 수의 제곱에 근사하므로 간단하게 표현하면 $O($|$V$|$^3)$이 됩니다. 이는 다익스트라 알고리즘($O($|$V$|$^2)$보다 더 큰데, 벨만-포드 알고리즘은 음수인 가중치까지 분석할 수 있기 때문에 일종의 trade-off라고 생각해도 될 것 같다는 생각이 듭니다.ㄴ

Comment  Read more