정점(Vertax)의 모음과 간선(Edge)의 모음의 결합
정점의 집합을 V, 간선의 집합을 E라고 했을때 그래프 G = (V, E)
인접(Adjacent): 간선으로 연결된 두 정점
경로(Path): 정점들 끼리의 길 ex) A-B-C
경로의 길이: 정점과 정점 사이에 있는 간선의 수
사이클(Cycle): 어느 경로가 한 정점을 두번이상 거치는 경우 -> 트리에서는 볼 수 없는 특징
방향성
방향성 그래프(Directed Graph)
무방향성 그래프(Undirected Graph)
연결성(Connectivity):
무방향성 그래프에서 두 정점 사이에 경로가 존재하면 두 정점은 연결되어있다.
그래프내의 각 정점이 다른 모든 정점과 연결되어 있다면 이 그래프는 연결되어있다.
https://www.geeksforgeeks.org/add-and-remove-edge-in-adjacency-matrix-representation-of-a-graph/
방문했음
으로 정한다.void DFS(Vertex* V) // 깊이 우선 탐색
{
Edge* adjList = V->AdjacencyList; // 매개변수로 받은 정점의 인접 정점 리스트
printf("%d ", V->Data);
V->Visited = Visited; // 매개변수로 받은 정점 방문
while (adjList != NULL)
{
if (adjList->Target != NULL && adjList->Target->Visited == NotVisited)
{
DFS(adjList->Target); // 재귀를 이용
}
adjList = adjList->Next; // 다음 인접정점으로
}
}
void BFS( Vertex* V, LinkedQueue* Queue )
{
// 시작 정점 큐에 넣기
V->Visited = Visited;
printf("%d ", V->Data);
LQ_Enqueue(Queue, LQ_CreateNode(V));
// 시작 정점에 대해한 작업 끝
Edge* E = NULL;
while (!LQ_IsEmpty(Queue)) // 큐가 빌때까지
{
V = LQ_Dequeue(Queue)->Data; // 큐 전단 정점 제거
E = V->AdjacencyList;
while (E != NULL) // 제거한(Dequeue) 정점의 인접 정점들에 대해서
{
if (E->Target != NULL && E->Target->Visited == NotVisited) // 인접정점중 방문하지 않은정점
{
E->Target->Visited = Visited; // 방문함으로 변경
printf("%d ", E->Target->Data);
LQ_Enqueue(Queue, LQ_CreateNode(E->Target)); // 큐에 넣기
}
E = E->Next; // 다음 인접정점
}
}
}
DAG Graph : 그래프에 방향성이 있고 그래프내에 사이클이 없는 그래프
한 노드에서 DFS를 시작하더라도 모든 노드를 방문할 수 있는 것은 아닙니다.
서로 연결되지 않는 두 그래프가 있다면, 한 그래프에서 시작한 DFS는 다른 그래프로 방문할 수 없습니다.
한 그래프에 있더라도, 방향 그래프인 경우 두 노드가 연결될 수 없는 경우도 존재합니다.
예를 들어 DAG(Directed Acyclic Graph, 유향 비순환 그래프) 의 중간에서 DFS를 시작하면,
시작 노드로 들어오는 엣지는 방문하지 못하기 때문에 모든 노드를 방문할 수 없습니다.
https://stlee321.tistory.com/entry/%EA%B7%B8%EB%9E%98%ED%94%84-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98DFS-%EA%B9%8A%EC%9D%B4-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89
- 임의정점을 시작 정점으로 하여 MST 뿌리 노드로 삽입
- 최소 신장 트리의 인근 노드들중 최소 신장 트리로 부터 가장 가중치가 작은 간선으로 연결된 노드를 삽입 (다만 사이클을 형성하면 안된다)
- 모든 정점이 연결되면 알고리즘 종료
void Prim(Graph* G, Vertex* StartVertex, Graph* MST) {
int i = 0; // index를 나타내기 위한 값
PQNode StartNode = { 0,StartVertex }; // {우선순위, 값}
PriorityQueue* PQ = PQ_Create(10); // PQ는 유동적으로 사이즈 조절 할 수 있음.
Vertex* CurrentVertex = NULL; // 현재 pop한 노드를 저장할 변수
Edge* CurrentEdge = NULL;
// 각정점들의 tree로 부터의 가중치를 저장할 배열
int* Weights = (int*)malloc(sizeof(int) * G->VertexCount);
// MST의 정점들을 인덱스에 따라 저장할 배열
Vertex** MSTVertices = (Vertex**)malloc(sizeof(Vertex*) * G->VertexCount);
// 색칠된 정점 즉 MST에 추가된게 확실해 더이상 확인 하지 않아도 되는 정점을 체크
Vertex** Fringes = (Vertex**)malloc(sizeof(Vertex*) * G->VertexCount);
// 한 정점의 부모 정점을 저장한 배열 : 한 정점의 인덱스를 주면 그 정점의 부모 정점을 바로 얻어낼 수 있다
Vertex** Precedences = (Vertex**)malloc(sizeof(Vertex*) * G->VertexCount);
CurrentVertex = G->Vertices; // MST를 만들 그래프의 정점 목록 가져오기
while (CurrentVertex != NULL) // 정점 등록 + 체크해야할 사항들을 초기값으로 초기화
{
Vertex* NewVertex = CreateVertex(CurrentVertex->Data);
AddVertex(MST, NewVertex);
Weights[i] = MAX_WEIGHT; // 무한대로 설정 (실제 코드에서는 충분히 큰 수로 표현)
MSTVertices[i] = NewVertex; // 인덱스에 맞는 정점
Fringes[i] = NULL;
Precedences[i] = NULL; // 아직 정해지지 않았으므로 null로 초기화
CurrentVertex = CurrentVertex->Next; // 다음 인덱스의 정점
i++;
}
PQ_Enqueue(PQ, StartNode);
Weights[StartVertex->Index] = 0; // 시작 노드에 대한 처리.
while (!PQ_IsEmpty(PQ)) // 간선 내용들 업데이트 -> 실질적인 MST 만드는 과정
{
PQNode Popped;
PQ_Dequeue(PQ, &Popped); // pq 최상단 노드 pop 해옴
CurrentVertex = Popped.Data;
CurrentEdge = CurrentVertex->AdjacencyList;
Fringes[CurrentVertex->Index] = CurrentVertex; // MST에 노드 들어감 -> 색칠됨
while (CurrentEdge != NULL)
{
Vertex* TargetVertex = CurrentEdge->Target;
// 아직 정점을 방문하지 않았고 알려진 가중치보다 새롭게 발견한 트리로부터의 가중치가 작다면
if (Fringes[TargetVertex->Index] == NULL && Weights[TargetVertex->Index] > CurrentEdge->Weight)
{
PQNode NewNode = { CurrentEdge->Weight,TargetVertex };
PQ_Enqueue(PQ, NewNode);
Weights[TargetVertex->Index] = CurrentEdge->Weight; // 가중치 업데이트
Precedences[TargetVertex->Index] = CurrentEdge->From; // 그 노드의 부모 업데이트
}
CurrentEdge = CurrentEdge->Next;
}
}
for (int i = 0; i < G->VertexCount; i++) // 간선 정보들을 이용해 간선만들기
{
int FromIndex = 0; int Toindex = 0;
if (Precedences[i] == NULL)
continue;
FromIndex = Precedences[i]->Index;
Toindex = i;
// 양방향으로 연결
AddEdge(MSTVertices[FromIndex], CreateEdge(MSTVertices[FromIndex], MSTVertices[Toindex], Weights[i]));
AddEdge(MSTVertices[Toindex], CreateEdge(MSTVertices[Toindex], MSTVertices[FromIndex], Weights[i]));
}
free(Weights);
free(MSTVertices);
free(Precedences);
free(Fringes);
PQ_Destroy(PQ);
}
- 그래프내의 모든 간선을 가중치의 오름차순으로 정렬(또는 PQ사용)
- 간선의 목록을 순회하면서 간선을 최소신장트리에 추가 단 사이클을 형성하면 안됨.
void Kruskal(Graph* G, Graph* MST)
{
int i = 0;
Vertex* CurrentVertex = NULL;
Vertex** MSTVertices = (Vertex**)malloc(sizeof(Vertex*) * G->VertexCount); // 정점목록
DisjointSet** VertexSet = (DisjointSet**)malloc(sizeof(DisjointSet*) * G->VertexCount); // 정점 갯수만큼의 분리집합 필요
PriorityQueue* PQ = PQ_Create(10); // 가장 가중치가 낮은 간선을 뽑아내기 위한 PQ
CurrentVertex = G->Vertices;
while (CurrentVertex != NULL)
{
Edge* CurrentEdge;
VertexSet[i] = DS_MakeSet(CurrentVertex); // 정점마다 분리집합 생성
MSTVertices[i] = CreateVertex(CurrentVertex->Data); // 정점 등록
AddVertex(MST, MSTVertices[i]);
CurrentEdge = CurrentVertex->AdjacencyList;
while (CurrentEdge != NULL) { // 모든 정점의 모든 간선을 PQ에 추가
PQNode newNode = { CurrentEdge->Weight,CurrentEdge };
PQ_Enqueue(PQ, newNode);
CurrentEdge = CurrentEdge->Next;
}
CurrentVertex = CurrentVertex->Next;
i++;
}
while (!PQ_IsEmpty(PQ))
{
PQNode Poped;
Edge* CurrentEdge = NULL;
int Fromindex = 0;
int Toindex = 0;
PQ_Dequeue(PQ, &Poped);
CurrentEdge = Poped.Data;
Fromindex = CurrentEdge->From->Index;
Toindex = CurrentEdge->Target->Index;
if (DS_FindSet(VertexSet[Fromindex]) != DS_FindSet(VertexSet[Toindex])) { // 간선 양 끝에 있는 정점이 같은 부분집합에 속해있는지 확인
AddEdge(MSTVertices[Toindex], CreateEdge(MSTVertices[Toindex], MSTVertices[Fromindex], CurrentEdge->Weight));
AddEdge(MSTVertices[Fromindex], CreateEdge(MSTVertices[Fromindex], MSTVertices[Toindex], CurrentEdge->Weight));
DS_UnionSet(VertexSet[Fromindex], VertexSet[Toindex]); // 간선을 연결했다면 합집합
}
}
for (int i = 0; i < G->VertexCount; i++)
{
DS_DestroySet(VertexSet[i]);
}
free(VertexSet);
free(MSTVertices);
}
- 각 정점은 시작점으로 부터 자신에게 이르는 거리를 저장할 곳 준비 초기값은 무한대(실제로는 매우 큰 수)
- 시작 정점은 0으로 초기화
- 최단 경로에 새로 추가된 정점의 인접 정점에 대해 경로 길이 갱신, 이들을 최단 경로에 추가
- 만약 추가하려는 인접 정점이 이미 최단 경로 안에 있다면 갱신 되기 이전의 경로 길이가 새로운 경로 길이보다 더 큰 경우에 한해 다른 선행 정점을 지나던 기존 경로를 현재 정점을 경유하도록 수정
- 그래프내의 모든 정점이 소속 될 때 까지 반복
void Dijkstra(Graph* G, Vertex* StartVertex, Graph* ShortestPath )
{
int i = 0;
PQNode StartNode = { 0, StartVertex };
PriorityQueue* PQ = PQ_Create(10);
Vertex* CurrentVertex = NULL;
Edge* CurrentEdge = NULL;
int* Weights = (int*) malloc( sizeof(int) * G->VertexCount );
Vertex** ShortestPathVertices =
(Vertex**) malloc( sizeof(Vertex*) * G->VertexCount );
Vertex** Fringes = (Vertex**) malloc( sizeof(Vertex*) * G->VertexCount );
Vertex** Precedences = (Vertex**) malloc( sizeof(Vertex*) * G->VertexCount );
CurrentVertex = G->Vertices;
while ( CurrentVertex != NULL )
{
Vertex* NewVertex = CreateVertex( CurrentVertex->Data );
AddVertex( ShortestPath, NewVertex);
Fringes[i] = NULL;
Precedences[i] = NULL;
ShortestPathVertices[i] = NewVertex;
Weights[i] = MAX_WEIGHT;
CurrentVertex = CurrentVertex->Next;
i++;
}
PQ_Enqueue ( PQ, StartNode );
Weights[StartVertex->Index] = 0; // 시작노드에서 시작노드까지의 거리는 0
while( ! PQ_IsEmpty( PQ ) )
{
PQNode Popped;
PQ_Dequeue(PQ, &Popped);
CurrentVertex = (Vertex*)Popped.Data;
Fringes[CurrentVertex->Index] = CurrentVertex;
CurrentEdge = CurrentVertex->AdjacencyList;
while ( CurrentEdge != NULL )
{
Vertex* TargetVertex = CurrentEdge->Target;
// 아직 방문하지 않았고
// 이웃정점의 알려진 거리의 합이 새롭게 발견한 경로의 가중치합보다 크다면 업데이트
if ( Fringes[TargetVertex->Index] == NULL &&
Weights[CurrentVertex->Index] + CurrentEdge->Weight <
Weights[TargetVertex->Index] )
{
PQNode NewNode = { CurrentEdge->Weight, TargetVertex };
PQ_Enqueue ( PQ, NewNode );
Precedences[TargetVertex->Index] = CurrentEdge->From; // 부모 노드 업데이트
// 정점의 가중치 + 엣지 가중치 = 엣지 끝의 정점의 가중치
Weights[TargetVertex->Index] =
Weights[CurrentVertex->Index] + CurrentEdge->Weight; // 더 작은값으로 업데이트
}
CurrentEdge = CurrentEdge->Next;
}
}
for ( i=0; i<G->VertexCount; i++ )
{
int FromIndex, ToIndex;
if ( Precedences[i] == NULL )
continue;
FromIndex = Precedences[i]->Index;
ToIndex = i;
// 방향성 그래프이므로 prim과 다르게 하나의 edge만 만듦
AddEdge( ShortestPathVertices[FromIndex],
CreateEdge(
ShortestPathVertices[FromIndex],
ShortestPathVertices[ToIndex],
Weights[i] ) ); // 여기서의 weight는 시작정점으로부터의 거리
}
free( Fringes );
free( Precedences );
free( ShortestPathVertices );
free( Weights );
PQ_Destroy( PQ );
}
정리가 잘 된 글이네요. 도움이 됐습니다.