내용
정점(Vertex)의 집합 V, 간선(Edge)의 집합 E가 있을 때
(V, E)쌍을 그래프(Graph)라고 한다.
A x B y C --> A에서 C로 가는 길
A j B k --> A정점에서 출발해 돌고돌아 A로 가는 길
┌---(j)---┐
A --(x)-- B --(y)-- C --(z)--┐
└---(k)---┘ | |
└--------┘
간선 x의 끝점 : A, B
정점 A의 부착간선 : j, x, k (얘들은 서로 병렬간선)
정점 A의 인접정점 : B
정점 A의 차수 : 3
정점 C는 루프가 있음 : 간선 z
경로 예시 : A x B y C
사이클 예시 : A j B x
(약속)
정점 개수 n
간선 개수 m
정점v의 차수 deg(v)
밀집도를 기준으로
연결성을 기준으로
싸이클이 있는 경우 연결성을 기준으로
그래프 G=(V,E)에 대해
그래프의 대표적인 구현 방법으로 아래 세 가지가 있다.
항목 | 간선리스트 | 인접리스트 | 인접행렬 |
---|---|---|---|
공간 | O(n+m) | O(n+m) | O(n^2) |
incidentEdges | O(m) | O(deg(v)) | O(n) |
adjacentVertices | O(m) | O(deg(v)) | O(n) |
areAdjacent | O(m) | O(min(deg(v), deg(w)) | O(1) |
insertVertex | O(1) | O(1) | O(n) |
insertEdge | O(1) | O(1) | O(1) |
removeVertex | O(m) | O(deg(v)) | O(n) |
removeEdge | O(1) | O(1) | O(1) |
인접리스트 구조의 정점,간선 리스트는 아래와 같이 표현할 수 있다.
typedef struct _Edge {
struct _Vertex* a;
struct _Vertex* b;
int w; // weight
struct _Edge* next;
} Edge;
typedef struct _IncidentEdge {
struct _Edge* edge; // Edge node
struct _IncidentEdge* next;
} IncidentEdge;
typedef struct _Vertex {
struct _IncidentEdge* incidentEdges;
int n; // node number
struct _Vertex* next;
} Vertex;
Vertex* VERTICES = getVertexNode(); // header node
Edge* EDGES = getEdgeNode(); // header node
인접행렬 구조의 정점, 간선 리스트와 인접행렬은 아래와 같이 표현할 수 있다.
typedef struct _Edge {
struct _Vertex* a;
struct _Vertex* b;
int w; // weight
struct _Edge* next;
} Edge;
typedef struct _Vertex {
int adjID; // 인접행렬 매트릭스에서 사용할 해당 정점의 ID
int n; // node number
struct _Vertex* next;
} Vertex;
Vertex* VERTICES = getVertexNode(); // 정점 리스트
Edge* EDGES = getEdgeNode(); // 간선 리스트
Edge* ADJS[VERTICES_NUM][VERTICES_NUM] = { NULL }; // 부착간선 매트릭스
그냥 연결리스트로 만들었던 것들을 다 배열에다가 박아넣으면 된다.
Vertex* 노드는 Vertex형 구조체 배열의 원소가 되고...
Vertex** 노드는 Vertex*형 구조체 배열의 원소가 되고...
(딱히 특별한게 없음)
문제점으로 가변 그래프에 대응하기가 귀찮다는 점을 들 수 있다.
모든 정점, 간선을 검사하는 절차
인접리스트로 구현하면 O(n+m)
인접행렬로 구현하면 O(n^2)이다.
DFS와 BFS모두 신장숲, 연결요소, 경로, 싸이클여부를 구할 수 있다.
최단경로는 BFS로, 이중 연결요소는 DFS로 구할 수 있다.
이진트리의 Pre-order순회와 유사.
순회 하다가 출발 정점으로 되돌하가기 위해 재귀 스택을 사용한다. 이는 재귀 호출로 자연히 구현된다.
각 정점과 간선은 상태를 갖는다.
DFS의 속성은 다음과 같다.
의사코드로 표현한 알고리즘은 다음과 같다.
Alg driver(G)
1. for each v ∈ G.vertices()
v.label ← Fresh
for each e ∈ G.edges()
e.label ← Fresh
2. for each v ∈ G.vertices()
if(v.label = Fresh)
DFS(G, v)
Alg DFS(G, v)
1. v.label ← Visited
2. for each e ∈ G.incidentEdges(v)
if(e.label = Fresh)
w ← G.opposite(v, e)
if(w.label = Fresh)
e.label ← Tree
DFS(G, w)
else
e.label ← Back
아래와 같이 C로 구현할 수 있다. (그래프는 인접리스트로 구현함)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
// 간선, 부착간선, 정점 노드 정의.
typedef struct _Edge {
struct _Vertex* a;
struct _Vertex* b;
int label; // 0:Fresh, 2:Tree, 3:Back
struct _Edge* next;
} Edge;
typedef struct _IncidentEdge {
struct _Edge* edge; // Edge node
struct _IncidentEdge* next;
} IncidentEdge;
typedef struct _Vertex {
struct _IncidentEdge* incidentEdges;
int n; // node number
int label; // 0:Fresh, 1:Visited
struct _Vertex* next;
} Vertex;
// 함수 원형 정의
Edge* getEdgeNode();
IncidentEdge* getIncidentEdgeNode();
Vertex* getVertexNode();
Vertex* findVertexNodeByNodenumber(int n);
Edge* insertEdge(Vertex* va, Vertex* vb);
IncidentEdge* insertIncidentEdge(Vertex* v, Edge* edge);
Vertex* insertVertex(int a);
Vertex* oppositeVertex(Vertex* v, Edge* e);
void DFS();
void rDFS(Vertex* v);
// -----------------
Vertex* VERTICES = NULL; // header node
Edge* EDGES = NULL; // header node
int main(){
VERTICES = getVertexNode(); // header node
EDGES = getEdgeNode(); // header node
int n, m, s;
scanf("%d%d%d", &n, &m, &s); getchar();
int a, b;
Vertex *va, *vb;
for(int i=0; i<m; i++){
va = NULL; vb = NULL;
scanf("%d%d", &a, &b); getchar();
// 입력받은 정점 번호에 대한 정점이 있으면 생성
va = findVertexNodeByNodenumber(a);
vb = findVertexNodeByNodenumber(b);
if(va == NULL) va = insertVertex(a);
if(vb == NULL) vb = insertVertex(b);
insertEdge(va, vb);
}
DFS(s);
return 0;
}
// --------------------------
// 새로운 노드를 할당하는 함수
Edge* getEdgeNode() {
Edge* p = (Edge*)malloc(sizeof(Edge));
p -> a = NULL;
p -> b = NULL;
p -> next = NULL;
return p;
}
IncidentEdge* getIncidentEdgeNode() {
IncidentEdge* p = (IncidentEdge*)malloc(sizeof(IncidentEdge));
p -> edge = NULL;
p -> next = NULL;
return p;
}
Vertex* getVertexNode() {
Vertex* p = (Vertex*)malloc(sizeof(Vertex));
p -> incidentEdges = getIncidentEdgeNode();
p -> next = NULL;
return p;
}
// --------------------------
// 기타 함수
void DFS(int s) { // s : 순회 시작 정점 번호
// 초기화
Vertex* u = VERTICES -> next;
while(u != NULL){
u -> label = 0; // Fresh
u = u -> next;
}
Edge* e = EDGES -> next;
while(e != NULL) {
e -> label = 0; // Fresh
e = e -> next;
}
// 정점번호가 s인 노드 - 첫 번째 노드 스왑.
Vertex* vs = findVertexNodeByNodenumber(s); // s번 정점
Vertex* vsPrev = VERTICES -> next;
while(vsPrev -> next != vs)
vsPrev = vsPrev -> next;
vsPrev -> next = vs -> next;
vs -> next = VERTICES -> next;
VERTICES -> next = vs;
// 순회 시작
u = VERTICES -> next;
while(u != NULL) {
if(u -> label == 0) // 정점 u가 Fresh이면
rDFS(u);
// TEST_printIncidentEdges(u);
u = u -> next;
}
}
void rDFS(Vertex* v) {
v -> label = 1; // Visited
printf("%d\n", v->n);
IncidentEdge* ie = (v -> incidentEdges)->next;
Edge* e = NULL;
Vertex* w = NULL;
while(ie != NULL) {
e = ie -> edge;
if(e -> label == 0) { // Fresh
w = oppositeVertex(v, e);
if(w -> label == 0) { // Fresh
e -> label = 2; // Tree
rDFS(w);
}
else {
e -> label = 3; // Back
}
}
ie = ie -> next;
}
}
Vertex* insertVertex(int a){
// 입력 : 노드 번호 a
Vertex* newVtx = getVertexNode();
newVtx -> n = a;
newVtx -> next = VERTICES -> next;
VERTICES -> next = newVtx;
return newVtx;
}
IncidentEdge* insertIncidentEdge(Vertex* v, Edge* edge) {
// 어떤 정점의 indidentEdge 연결리스트에 새로운 간선 추가. (반대편 노드 오름차순 순서에 맞게 추가)
// 입력 : 추가대상 정점 v, 추가할 간선 edge
IncidentEdge* newie = getIncidentEdgeNode();
newie -> edge = edge;
// 새로운 incedentEdge(newie)를 삽입할 위치를 찾는다.
// while문이 끝난 뒤 q 노드, p 노드 사이에 삽입한다.
IncidentEdge* p = (v -> incidentEdges) -> next;
IncidentEdge* q = NULL; // p의 직전 노드
Vertex* newieOppositeVtx = oppositeVertex(v, edge);
int newieOppositeVtxNum = newieOppositeVtx -> n;
Vertex* existOppositeVtx = NULL;
int existOppositeVtxNum = 0;
while(p != NULL){
existOppositeVtx = oppositeVertex(v, p->edge);
existOppositeVtxNum = existOppositeVtx -> n;
if (existOppositeVtxNum > newieOppositeVtxNum)
break;
q = p;
p = p -> next;
}
if(q == NULL) // 만약 v -> incidentEdges가 비었으면(헤더밖에 없으면)
q = v -> incidentEdges;
// 삽입
newie -> next = q -> next;
q -> next = newie;
return newie;
}
Edge* insertEdge(Vertex* va, Vertex* vb) {
// 입력 : 정점노드 a, b
Edge* newEdge = getEdgeNode();
newEdge -> a = va;
newEdge -> b = vb;
newEdge -> next = EDGES -> next;
EDGES -> next = newEdge;
insertIncidentEdge(va, newEdge);
if(va != vb)
insertIncidentEdge(vb, newEdge);
return newEdge;
}
Vertex* findVertexNodeByNodenumber(int n) {
// 노드번호 n에 해당하는 정점 찾기
// 정점이 있으면 해당 노드 반환, 없으면 NULL 반환
Vertex* v = VERTICES -> next;
while(v != NULL){
if(v -> n == n)
break;
v = v -> next;
}
return v;
}
Vertex* oppositeVertex(Vertex* v, Edge* e){
// 정점 v의 간선 e 건너편에 있는 정점을 리턴
if(e -> a == v)
return e -> b;
else
return e -> a;
}
이진트리의 레벨순회와 유사하다.
DFS와의 차이는 아래와 같이 정리할 수 있다.
정점과 간선은 아래 상태를 가진다.
BFS를 돌고 나면 그래프는 레벨별로 리스트를 갖게 된다.
BFS의 속성은 다음과 같다.
의사코드로 표현된 알고리즘은 아래와 같다. (그래프는 인접행렬로 구현함)
Alg driver(G)
1. for each v ∈ G.vertices()
v.label ← Fresh
for each e ∈ G.edges()
e.label ← Fresh
2. for each v ∈ G.vertices()
if(v.label = Fresh)
BFS(G, v)
Alg BFS(G, v)
1. L_0 ← 빈 리스트
L_0.addLast(v)
v.label ← Visited
2. i ← 0
3. while(!L_i.isEmpty())
L_(i+1) ← 빈 리스트
for each v ∈ L_i.elements()
for each e ∈ G.incidentEdges(v)
if(e.label = Fresh)
w ← G.opposite(v, e)
if(w.label = Fresh)
e.label ← Tree
w.label ← Visited
L_(i+1).addLast(w)
else
e.label ← Cross
C로 구현된 BFS는 아래와 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
// 간선, 정점 노드 정의.
typedef struct _Edge {
struct _Vertex* a;
struct _Vertex* b;
int label; // 0:Fresh, 2:Tree, 3:Cross
struct _Edge* next;
} Edge;
typedef struct _Vertex {
int adjID; // 인접행렬 매트릭스에서 사용할 해당 정점의 ID
int n; // node number
int label; // 0:Fresh, 1:Visited
struct _Vertex* next;
} Vertex;
// 함수 원형 정의
Edge* getEdgeNode();
Vertex* getVertexNode();
Vertex* findVertexNodeByNodenumber(int n);
Edge* insertEdge(Vertex* va, Vertex* vb);
void insertIncidentEdge(int aAdjID, int bAdjID, Edge* e);
Vertex* insertVertex(int a);
Vertex* oppositeVertex(int nodenum, Edge* e);
void BFS();
void BFSI(Vertex* v);
void insertVertexIntoLevelContainer(int i, Vertex* v);
// -----------------
Vertex* VERTICES = NULL; // 정점 리스트
Edge* EDGES = NULL; // 간선 리스트
Edge* ADJS[101][101] = { NULL };
Vertex** LEVEL_CONTAINERS = NULL; // 레벨 컨테이너의 리스트
int VTX_NUM = 0;
int main(){
// n, m, s 입력
int n, m, s;
scanf("%d%d%d", &n, &m, &s); getchar();
VTX_NUM = n;
// 정점, 간선 리스트 초기화.
VERTICES = getVertexNode(); // header node
EDGES = getEdgeNode(); // header node
// BFS 순회시 각 레벨별 노드를 보관할 컨테이너 초기화
LEVEL_CONTAINERS = (Vertex**)malloc(sizeof(Vertex*)*VTX_NUM);
for(int i=0; i<VTX_NUM; i++)
LEVEL_CONTAINERS[i] = getVertexNode();
// 정점, 간선 입력.
int a, b;
Vertex *va, *vb;
for(int i=0; i<m; i++){
va = NULL; vb = NULL;
scanf("%d%d", &a, &b); getchar();
// 입력받은 정점 번호에 대한 정점이 없으면 생성
va = findVertexNodeByNodenumber(a);
vb = findVertexNodeByNodenumber(b);
if(va == NULL) va = insertVertex(a);
if(vb == NULL) vb = insertVertex(b);
insertEdge(va, vb);
}
// 순회
BFS(s);
return 0;
}
// --------------------------
// 새로운 노드를 할당하는 함수
Edge* getEdgeNode() {
Edge* p = (Edge*)malloc(sizeof(Edge));
p -> a = NULL;
p -> b = NULL;
p -> next = NULL;
return p;
}
Vertex* getVertexNode() {
Vertex* p = (Vertex*)malloc(sizeof(Vertex));
p -> next = NULL;
return p;
}
// --------------------------
// 기타 함수
void BFS(int s) { // s : 순회 시작 정점 번호
// 초기화
Vertex* u = VERTICES -> next;
while(u != NULL){
u -> label = 0; // Fresh
u = u -> next;
}
Edge* e = EDGES -> next;
while(e != NULL) {
e -> label = 0; // Fresh
e = e -> next;
}
// s번 정점노드와 첫 번째 정점노드 스왑
// ---- VERTICES리스트에 대해서
Vertex* vs = findVertexNodeByNodenumber(s); // s번 정점
Vertex* vsPrev = VERTICES -> next;
while(vsPrev -> next != vs)
vsPrev = vsPrev -> next;
vsPrev -> next = vs -> next;
vs -> next = VERTICES -> next;
VERTICES -> next = vs;
// 순회
u = VERTICES -> next;
while(u != NULL) {
if(u -> label == 0) // 정점 u가 Fresh이면
BFSI(u);
u = u -> next;
}
}
void BFSI(Vertex* u) {
int i = 0;
u -> label = 1; // visited
printf("%d\n", u->n);
insertVertexIntoLevelContainer(i, u);
Vertex* element = NULL;
Edge* edge = NULL;
while(LEVEL_CONTAINERS[i]->next != NULL) { // if L_i is not empty
element = LEVEL_CONTAINERS[i] -> next; // L_i의 원소(정점) 조회
while(element != NULL) {
for(int k=0; k<=100; k++){
edge = ADJS[element->adjID][k]; // 부착간선 조회
if(edge == NULL) continue;
if(edge -> label == 0){ // l(e) = Fresh
Vertex* w = oppositeVertex(element->n, edge);
if(w -> label == 0) { // l(w) = Fresh
edge -> label = 2; // Tree
w -> label = 1; // Visited
printf("%d\n", w->n);
insertVertexIntoLevelContainer(i+1, w);
}
else {
edge -> label = 3; // Cross
}
}
}
element = element -> next;
}
i++;
}
}
void insertVertexIntoLevelContainer(int i, Vertex* v) {
// i 레벨 컨테이너의 가장 뒤에 정점 복사본 삽입
// 원본(v)와 복사본(copiedVtx)는 next필드값만 다름.
Vertex* copiedVtx = getVertexNode();
copiedVtx -> adjID = v -> adjID;
copiedVtx -> n = v -> n;
copiedVtx -> label = v -> label;
Vertex* p = LEVEL_CONTAINERS[i] -> next;
Vertex* q = LEVEL_CONTAINERS[i];
while(p != NULL){
q = p;
p = p -> next;
}
copiedVtx -> next = NULL;
q -> next = copiedVtx;
}
Vertex* insertVertex(int a){
// 입력 : 노드 번호 a
Vertex* p = getVertexNode();
p -> adjID = a-1;
p -> n = a;
p -> next = VERTICES -> next;
VERTICES -> next = p;
return p;
}
void insertIncidentEdge(int aAdjID, int bAdjID, Edge* e){
ADJS[aAdjID][bAdjID] = e;
ADJS[bAdjID][aAdjID] = e;
}
Edge* insertEdge(Vertex* va, Vertex* vb) {
// 입력 : 정점 노드 va, vb
Edge* newEdge = getEdgeNode();
newEdge -> a = va;
newEdge -> b = vb;
newEdge -> next = EDGES -> next;
EDGES -> next = newEdge;
insertIncidentEdge(va->adjID, vb->adjID, newEdge);
return newEdge;
}
Vertex* findVertexNodeByNodenumber(int n) {
// 노드번호 n에 해당하는 정점 찾기
// 정점이 있으면 해당 노드 반환, 없으면 NULL 반환
Vertex* v = VERTICES -> next;
while(v != NULL){
if(v -> n == n)
break;
v = v -> next;
}
return v;
}
Vertex* oppositeVertex(int nodenum, Edge* e){
// 정점 v의 간선 e 건너편에 있는 정점을 리턴
if((e -> a) -> n == nodenum)
return e -> b;
else
return e -> a;
}
모든 간선이 방향간선인 그래프
(a,b)
는 a에서 b로만 간다.DFS/BFS를 방향그래프에 특화한 것으로, 주어진 간선 방향으로만 순회한다.
간선은 아래 네 가지 상태를 가진다.
Back
┌───→ A ────→ C
| ↙ \
| B |
\ ↙ ↘ ↙ Forward
D ← E
Cross
표시 안된 간선 : Tree
D->E : Cross
D->A : Back
A->E : Forward
강연결은 그래프 전체에 도달 가능성이 있는 것으로 생각할 수 있다.
강연결을 검사하기 위해 DFS를 사용할 수 있다.
이 때, 모든 정점의 강연결을 검사하지 않고도 전체 그래프가 강연결인지 여부를 판단할 수 있다.
시간 복잡도 O(2n+2m) = O(n+m)에 해결할 수 있다.
Alg isStronglyConnected(G)
1. v ← G.aVertex() # 임의의 정점 하나 선택
# v에서 어디로 갈 수 있냐?
2. DFS(G, v)
3. for each u ∈ G.vertices()
if(u.label = Fresh)
return False # 삑! 강연결이 아닙니다.
# 어디에서 v로 갈 수 있냐?
4. for each e ∈ G.edges() # 그래프의 모든 간선방향 반대로 역행
G.reverseDirection(e)
DFS(G, v)
5. for each u ∈ G.vertices()
if(u.label = Fresh)
return False # 삑! 강연결이 아닙니다.
# 강연결로 판단됨
6. return True
강연결 요소를 구하는데에도 DFS를 사용할 수 있다. 강연결 검사와 마찬가지로 O(n+m)시간에 해결 가능하다.
이행적 폐쇄는 방향그래프 G에 대해 다음을 만족하는 방향그래프로, 도달가능성 정보를 제공한다.
그래프 G
+-+ +-+ +-+
|A| ------> |B| ------> |C|
+-+ +-+ +-+
| +-+
+----> |D|
+-+
그래프 G' (이행적 폐쇄)
+-----------------------+
| ↓
+-+ +-+ +-+
|A| ------> |B| ------> |C|
+-+ +-+ +-+
| | +-+
| +----> |D|
| +-+
| ↑
+-------------------+
이행적 폐쇄를 구하기 위해(계산하기 위해) 두 가지 방법을 사용할 수 있다
플로이드 워셜 알고리즘을 의사코드로 표현하면 아래와 같다.
Alg Floyd-Warshall(G)
1. G_0 ← G
2. for k ← 1 to n
G_k ← G_(k-1)
for i ← 1 to n, i ≠ k
for j ← 1 to n, j ≠ i, k
if(G_(k-1).areAdjacent(v_i, v_k) & G_(k-1).areAdjacent(v_k, v_j))
if(!G_k.areAdjacent(v_i, v_j))
G_k.insertDirectedEdge(v_i, v_j, k)
3. return G_n
방향그래프가 DAG이면 위상순서를 가진다. 역도 성립한다.
위상정렬 수행 과정에서 사이클이 발견되면 위상순서가 없음을 알 수 있다.
위상 정렬은 두 가지로 구현할 수 있다.
정점의 진입차수를 이용한 위상정렬 알고리즘은 아래와 같다.
Alg topologicalSort_InDegree(G)
1. Q ← 빈 큐
2. for each u ∈ G.vertices()
u.inCount ← G.inDegree(u)
if(u.inCount = 0)
Q.enqueue(u)
3. i ← 1
4. while(!Q.isEmpty())
u ← Q.dequeue()
u.topologicalNum ← i
i ← i+1
for each e ∈ G.outIncidentEdges(u)
w ← G.opposite(u, e)
w.inCount ← w.inCount - 1
if(w.inCount = 0)
Q.enqueue()
5. if(i≤n)
return False # G는 DAG가 아니다. (사이클이 있다.)
else
return True # G는 DAG이므로 정상적으로 위상정렬 완료되었다.
DFS를 이용한 위상정렬 알고리즘은 아래와 같다.
Alg driver(G)
1. n ← G.numVertices()
2. for each u ∈ G.vertices()
u.label ← Fresh
u.topologicalNum ← NULL
3. for each v ∈ G.vertices()
if(v.label = Fresh)
topologicalSort_DFS(G, v)
Alg topologicalSort_DFS(G, v)
1. v ← Visited
2. for each e ∈ G.outIncidentEdges(v)
w ← opposite(v, e)
if(w.label = Fresh)
topologicalSort_DFS(G, w)
elseif(w.topologicalNum = NULL) # 방문했는데 위상순서 번호가 없다
return False # G는 DAG가 아니다.
3. v.topologicalNum ← n
n ← n-1
최소신장트리는 가중 그래프의 총 간선 무게가 최소인 신장트리이다.
(대충 그래프의 정점은 다 취하고 간선은 일부만 취했는데, 간선 무게 더한게 최소가 된다는 소리)
MST 계산을 위해 세 가지 알고리즘을 사용할 수 있다.
아래에 설명해둔 알고리즘은 모두 단순 연결 무방향 가중그래프에 탐욕법을 적용한다. 성능은 모두 이다.
탐욕법은 탐욕적 선택 속성
을 가진 문제에 적용할 수 있다.
전체적인 이득을 생각하지 않고 지역적인(그때그때 상황의) 이득을 택하는 것이다.
시작할 때 모든 정점의 거리는 무한대로 초기화하고, 임의의 시작정점에 대해서만 0으로 설정한다.
각 정점은 아래 두 속성을 갖는다.
Prim-Jarnik은 (배낭 밖의)모든 정점을 우선순위큐에 넣고 시작한다. 우선순위큐는 최소힙으로 구현할 수 있다. 힙의 키는 정점의 거리자, 원소는 정점이다.
while문에서 실질적인 알고리즘이 동작한다. 힙에서 거리자가 가장 작은 정점을 하나 빼온뒤, 인접정점을 하나씩 꺼내 거리자와 부모간선 값을 변경한다.
Alg PrimJarnik(G)
1. for each v ∈ G.vertices()
v.distance ← ∞
v.parent ← NULL
2. s ← G.aVertex()
s.distance ← 0
3. Q ← 빈 우선순위 큐 (힙)
for each v ∈ G.vertices()
Q.insertItem(v.distance, v)
4. while(!Q.isEmpty())
u ← Q.removeMin()
for each e ∈ G.incidentEdges(u)
z ← G.opposite(e, u)
if( (z∈Q) & (e.weight < z.distance) )
z.distance ← e.weight
z.parent ← e
Q.replaceKey(z, e.weight)
Prim-Jarnik에서는 while문을 돌 때마다 정점과 간선을 하나씩 포함시키며 배낭을 확장한다. 배낭 밖의 정점은 우선순위큐에 넣고 시작했다.
Kruskal은 Prim-Jarnik과 다르게 각 정점을 개별 배낭에 넣고 시작한다. 여기서 개별 배낭은 분리집합으로 구현할 수 있다. 우선순위 큐에는 배낭 밖의 간선이 들어간다.
항목 | Prim-Jarnik | Kruskal |
---|---|---|
초기 배낭 개수 | 1 | 정점 개수 |
초기 배낭이 담는 것 | 시작 정점 | 배낭 1개당 정점 1개 |
우선순위큐가 담는 것 | 배낭 밖 정점 | 배낭 밖 간선 |
알고리즘이 동작함에 따라 두 배낭과 그 사이의 간선을 하나의 배낭으로 합친다.
의사코드로 표현된 알고리즘은 다음과 같다.
Alg Kruskal(G)
1. for each v ∈ G.vertices()
Bag(v) ← {v}
Q ← 빈 우선순위 큐
for each e ∈ G.edges()
Q.insertItem(e.weight, e)
T ← NULL # MST를 구성하는 하나의 배낭
2. while(T의 간선 개수 < n-1)
e ← Q.removeMin()
if( Bag(e.u) ≠ Bag(e.v) )
T.add(e)
Merge(Bag(e.u), Bag(e.v))
3. return T
우선순위큐를 사용하지 않는다.
Alg Baruvka(G)
1. T ← V
2. while( T의 간선 개수 < n-1 )
for each (T안의 연결요소 C_i)
e ← C_i에서 T안의 다른 연결요소로 연결되는, 무게가 가장 작은 간선
if( e ∉ T )
T.add(e)
3. return T
최단경로는 두 개의 정점 사이 무게 합이 최소인 경로이다.
최단경로 계산 알고리즘별로 적용 가능한 그래프와 성능이 다르다.
알고리즘 | 적용 가능한 그래프 | 성능 |
---|---|---|
다익스트라 | {e∈E | e.w≥0} 인 그래프 | O(m logn) or O(n^2) |
벨만포드 | 방향그래프 | O(nm) |
BFS | 비가중그래프 | O(n+m) |
위상 순서 | DAG | O(n+m) |
s는 임의로 지정한 출발정점이다.
다익스트라를 돌리면 s로부터 모든 정점으로의 최단경로를 구한다.
우선순위큐는 힙으로 구현할 수 있다. replaceKey는 해당 정점에 대한 upHeap이나 다름없다.
Alg Dijkstra(G)
1. for each v ∈ G.vertices()
v.distance ← INFINITE
s.distance ← 0
2. Q ← 빈 우선순위 큐
for each v ∈ G.vertices()
Q.insertItem(v.distance, v)
3. while(!Q.isEmpty())
u ← Q.removeMin()
for each e ∈ G.incidentEdges(u)
z ← G.opposite(u, e)
if(z ∈ Q.elements())
if(u.distance + e.weight < z.distance)
z.distance ← u.distance + e.weight
Q.replaceKey(z, z.distance)
Alg Bellman-Ford(G, s)
1. for each v ∈ G.vertices()
v.distance ← INFINITE
s.distance ← 0
2. for i ← 1 to n-1
for each e ∈ G.edges()
u ← G.origin(e)
z ← G.opposite(u, e)
z.distance ← min(z.distance, u.distance+e.weight)
그래프 G는 위상정렬이 완료되어 각 정점이 순서를 갖고 있다고 가정한다.
v_i는 i번째 위상순서의 정점이다.
Alg TopologicalOrder_ShortestPath(G, s)
1. for each v ∈ G.vertices()
v.distance ← INFINITE
s.distance ← 0
2. for i ← 1 to n-1
for each e ∈ G.outIncidentEdges(v_i)
z ← G.opposite(v_i, e)
z.distance ← min(z.distance, v_i.distance + e.weight)
가중 방향그래프의 모든 정점사이의 최단경로 거리를 찾을 수 있다.
다익스트라, 벨만포드를 모든 정점에 대해(=n번) 호출할 수 있다.
또는 플로이드-워셜과 유사한 알고리즘을 사용해 O(n^3)시간에 찾을 수 있다. 이 방법을 의사코드로 나타내면 아래와 같다.
알고리즘을 돌리기 전 모든 정점에 대해 임의의 번호 1~n을 붙여두어야 한다.
Alg allPairsShortestDistance(G)
1. for i ← 1 to n
for j ← 1 to n
if(i=j)
D[i][j] ← 0
elseif( (v_i, v_j) ∈ G.edges() )
D[i][j] ← w(v_i, v_j)
else
D[i][j] ← INFINITE
2. for k ← 1 to n
for i ← 1 to n
for j ← 1 to n
D[i][j] ← min(D[i][j], D[i][k]+D[k][j])