// Dijkstra.h
#pragma once
#include <queue>
#include "List.h"
#include "Heap.h"
#include <assert.h>
template <typename T>
class CEdge
{
template <typename T>
friend class CDijkstraNode;
template <typename T>
friend class CDijkstra;
private:
CEdge()
{
m_Node = nullptr;
m_Cost = 0;
}
~CEdge()
{
}
private:
CDijkstraNode<T>* m_Node;
int m_Cost;
};
template <typename T>
class CDijkstraNode
{
template <typename T>
friend class CDijkstra;
private:
CDijkstraNode()
{
m_Size = 0;
m_Capacity = 3;
m_Visit = false;
m_Cost = INT_MAX;
m_Parent = nullptr;
m_EdgeArray = new CEdge<T>*[m_Capacity];
}
~CDijkstraNode()
{
for (int i = 0; i < m_Size; ++i)
{
delete m_EdgeArray[i];
}
delete[] m_EdgeArray;
}
private:
T m_Data;
// 이 노드에 연결된 Edge 포인터 배열
CEdge<T>** m_EdgeArray;
// 나중에 도착노드에서 출발해서 각 노드의 m_Parent를 타고
// 출발지점까지 가는게 최단 경로가 되도록 하기 위해 선언한 멤버
CDijkstraNode<T>* m_Parent;
// 나와 연결된 엣지 개수( = 연결된 노드 개수)
int m_Size;
int m_Capacity;
bool m_Visit;
int m_Cost;
private:
// 나와 인자로 들어온 Node를 이어주는 edge를 만들고
// 두개를 서로 이어준다
void ConnectNode(CDijkstraNode<T>* Node, int Cost)
{
if (!Node)
{
assert(Node != nullptr);
}
if (m_Size == m_Capacity)
{
m_Capacity *= 2;
CEdge<T>** tmp = new CEdge<T>*[m_Capacity];
memcpy(tmp, m_EdgeArray, sizeof(CEdge<T>*) * m_Size);
delete[] m_EdgeArray;
m_EdgeArray = tmp;
}
CEdge<T>* NewEdge = new CEdge<T>;
m_EdgeArray[m_Size] = NewEdge;
NewEdge->m_Cost = Cost;
NewEdge->m_Node = Node;
++m_Size;
}
};
template <typename T>
class CDijkstra
{
public:
CDijkstra()
{
m_Size = 0;
m_Capacity = 10;
m_NodeArray = new CDijkstraNode<T>*[m_Capacity];
}
~CDijkstra()
{
for (int i = 0; i < m_Size; ++i)
{
delete m_NodeArray[i];
}
delete[] m_NodeArray;
}
private:
int m_Size;
int m_Capacity;
CDijkstraNode<T>** m_NodeArray;
public:
void insert(const T& Data)
{
if (m_Size == m_Capacity)
{
m_Capacity *= 2;
CDijkstraNode<T>** Array = new CDijkstraNode<T>*[m_Capacity];
memcpy(Array, m_NodeArray, sizeof(CDijkstraNode<T>*) * m_Size);
delete[] m_NodeArray;
m_NodeArray = Array;
}
CDijkstraNode<T>* NewNode = new CDijkstraNode<T>;
NewNode->m_Data = Data;
m_NodeArray[m_Size] = NewNode;
++m_Size;
}
void AddEdge(const T& Src, const T& Dest, int Cost)
{
CDijkstraNode<T>* SrcNode = FindNode(Src);
CDijkstraNode<T>* DestNode = FindNode(Dest);
SrcNode->ConnectNode(DestNode, Cost);
DestNode->ConnectNode(SrcNode, Cost);
}
bool FindRoute(const T& Src, const T& Dest, CList<T>* Result)
{
CDijkstraNode<T>* SrcNode = FindNode(Src);
if (!SrcNode)
return false;
CDijkstraNode<T>* DestNode = FindNode(Dest);
if (!DestNode)
return false;
SrcNode->m_Visit = true;
SrcNode->m_Cost = 0;
CHeapsort<CDijkstraNode<T>*> heap;
heap.SetSortFunction(SortNode);
heap.PushData(SrcNode);
while (!heap.empty())
{
int Size = heap.GetSize();
heap.Heapsort();
// Heapsort 호출하고 나면 m_Size = 0이 돼서 다시 원래 size로 복귀시켜주는 코드
heap.SetSize(Size);
CDijkstraNode<T>* SearchNode = heap.GetTop();
std::cout << SearchNode->m_Data << std::endl;
heap.Pop();
for (int i = 0; i < SearchNode->m_Size; ++i)
{
if (SearchNode->m_EdgeArray[i]->m_Node->m_Cost > SearchNode->m_Cost + SearchNode->m_EdgeArray[i]->m_Cost)
{
SearchNode->m_EdgeArray[i]->m_Node->m_Cost = SearchNode->m_Cost + SearchNode->m_EdgeArray[i]->m_Cost;
SearchNode->m_EdgeArray[i]->m_Node->m_Parent = SearchNode;
}
if (SearchNode->m_EdgeArray[i]->m_Node->m_Visit == false)
{
heap.PushData(SearchNode->m_EdgeArray[i]->m_Node);
heap.MaxHeapify(1);
SearchNode->m_EdgeArray[i]->m_Node->m_Visit = true;
}
}
}
CDijkstraNode<T>* Node = DestNode;
while (Node)
{
Result->push_front(Node->m_Data);
Node = Node->m_Parent;
}
return true;
}
private:
CDijkstraNode<T>* FindNode(const T& data)
{
for (int i = 0; i < m_Size; ++i)
{
if (m_NodeArray[i]->m_Data == data)
return m_NodeArray[i];
}
// 여기에 도달한다는 것은 못찾았다는 의미
assert(false);
}
static bool SortNode(CDijkstraNode<T>* const& Left, CDijkstraNode<T>* const& Right)
{
return Left->m_Cost > Right->m_Cost;
}
};
// Heap.h
#pragma once
#include <cmath>
#include <assert.h>
template <typename T>
class CHeapsort
{
public:
CHeapsort()
{
m_Capacity = 5;
m_Size = 0;
//std::cout << typeid(m_Data).name() << std::endl;
m_Data = new T[m_Capacity];
}
~CHeapsort()
{
delete[] m_Data;
}
private:
T* m_Data;
int m_Size;
int m_Capacity;
bool (*m_Func)(const T&, const T&);
public:
void MaxHeapify(int Index)
{
int Left = Index * 2;
int Right = Index * 2 + 1;
int LargeIndex = Index;
if (Left <= m_Size && m_Func(m_Data[Left], m_Data[LargeIndex]))
{
LargeIndex = Left;
}
if (Right <= m_Size && m_Func(m_Data[Right], m_Data[LargeIndex]))
{
LargeIndex = Right;
}
if (LargeIndex != Index)
{
T tmp = m_Data[LargeIndex];
m_Data[LargeIndex] = m_Data[Index];
m_Data[Index] = tmp;
MaxHeapify(LargeIndex);
}
}
private:
void BuildMaxHeap()
{
for (int i = floor(m_Size / 2); i > 0; --i)
{
MaxHeapify(i);
}
}
public:
// heap에 데이터를 넣기만 하는 함수(정렬은 Heapsort 호출해야함)
// 인덱스 1부터 넣는다
void PushData(const T& Data)
{
if (m_Size + 1 == m_Capacity)
{
m_Capacity *= 2;
T* tmp = new T[m_Capacity];
memcpy(tmp, m_Data, sizeof(T) * m_Capacity);
delete[] m_Data;
m_Data = tmp;
}
++m_Size;
m_Data[m_Size] = Data;
}
void Heapsort()
{
BuildMaxHeap();
for (int i = m_Size; i > 1; --i)
{
T tmp = m_Data[i];
m_Data[i] = m_Data[1];
m_Data[1] = tmp;
--m_Size;
MaxHeapify(1);
}
}
void Pop()
{
if (empty())
assert(false);
else if (m_Size == 1)
{
m_Size = 0;
return;
}
// 가장 마지막에 추가된 노드를 루트로 올린다.
m_Data[1] = m_Data[m_Size];
--m_Size;
MaxHeapify(1);
}
int GetSize() const
{
return m_Size;
}
void SetSize(int Size)
{
m_Size = Size;
}
int GetCapacity() const
{
return m_Capacity;
}
T GetTop() const
{
return m_Data[1];
}
T* GetData() const
{
return m_Data;
}
T GetData(int Index) const
{
return m_Data[Index];
}
bool empty() const
{
return m_Size == 0;
}
void SetSortFunction(bool (*pFunc)(const T&, const T&))
{
m_Func = pFunc;
}
};
// List.h
#pragma once
#include <assert.h>
// friend : 아래처럼 Listnode에 List를 friend로 지정하면 List에서는 ListNode의
// private에 접근할 수 있다.
template <typename T>
class CListNode
{
template <typename T>
friend class CList;
template <typename T>
friend class CListIterator;
template <typename T>
friend class CListReverseIterator;
private:
CListNode() :
m_Next(nullptr),
m_Prev(nullptr)
{
}
~CListNode()
{
}
private:
T m_Data;
CListNode<T>* m_Next;
CListNode<T>* m_Prev;
};
/*
iterator : 노드를 순차적으로 방문하기 위해서 iterator를 만들어서 제공한다.
iterator를 이용해서 앞에서 부터 혹은 뒤에서부터 노드를 순차적으로 접근할 수 있게 해준다.
*/
template <typename T>
class CListIterator
{
template <typename T>
friend class CList;
public:
CListIterator() :
m_Node(nullptr)
{
}
~CListIterator()
{
}
private:
CListNode<T>* m_Node;
public:
// iterator끼리 서로 가지고 있는 노드가 같을 경우 같다고 판단한다.
bool operator == (const CListIterator<T>& iter) const
{
return m_Node == iter.m_Node;
}
bool operator != (const CListIterator<T>& iter) const
{
return m_Node != iter.m_Node;
}
bool operator == (const CListNode<T>* Node) const
{
return m_Node == Node;
}
bool operator != (const CListNode<T>* Node) const
{
return m_Node != Node;
}
void operator ++ ()
{
m_Node = m_Node->m_Next;
}
void operator ++ (int)
{
m_Node = m_Node->m_Next;
}
void operator -- ()
{
m_Node = m_Node->m_Prev;
}
void operator -- (int)
{
m_Node = m_Node->m_Prev;
}
T& operator * () const
{
return m_Node->m_Data;
}
};
// 숙제 : reverseiterator 클래스를 만들어오기.
// reverseiterator 는 역방향으로 진행하는 iterator이다.
template <typename T>
class CListReverseIterator
{
template <typename T>
friend class CList;
public:
CListReverseIterator() :
m_Node(nullptr)
{
}
~CListReverseIterator()
{
}
private:
CListNode<T>* m_Node;
public:
// iterator끼리 서로 가지고 있는 노드가 같을 경우 같다고 판단한다.
bool operator == (const CListReverseIterator<T>& iter) const
{
return m_Node == iter.m_Node;
}
bool operator != (const CListReverseIterator<T>& iter) const
{
return m_Node != iter.m_Node;
}
bool operator == (const CListNode<T>* Node) const
{
return m_Node == Node;
}
bool operator != (const CListNode<T>* Node) const
{
return m_Node != Node;
}
void operator ++ ()
{
m_Node = m_Node->m_Prev;
}
void operator ++ (int)
{
m_Node = m_Node->m_Prev;
}
void operator -- ()
{
m_Node = m_Node->m_Next;
}
void operator -- (int)
{
m_Node = m_Node->m_Next;
}
T& operator * () const
{
return m_Node->m_Data;
}
};
template <typename T>
class CList
{
public:
CList()
{
m_Size = 0;
// Begin과 End노드를 생성하고 두 노드를 서로 연결한다.
m_Begin = new NODE;
m_End = new NODE;
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
}
~CList()
{
// Begin노드는 이전노드가 nullptr이다.
// End노드는 다음노드가 nullptr이다.
PNODE DeleteNode = m_Begin;
while (DeleteNode)
{
PNODE Next = DeleteNode->m_Next;
delete DeleteNode;
DeleteNode = Next;
}
}
private:
typedef CListNode<T> NODE;
typedef CListNode<T>* PNODE;
public:
typedef CListIterator<T> iterator;
typedef CListReverseIterator<T> reverse_iterator;
private:
// Begin과 End는 데이터를 저장하기 위한 노드는 아니다.
// 명시적으로 시작과 끝을 의미하는 노드로 사용하기 위해 할당해두고 사용한다.
// 실제 데이터를 저장하는 노드는 Begin과 End노드 사이에 위치하게 될것이다.
PNODE m_Begin;
PNODE m_End;
int m_Size;
public:
void push_back(const T& data)
{
// 데이터를 저장해둘 노드를 생성한다.
PNODE Node = new NODE;
Node->m_Data = data;
// End노드와 End노드의 이전노드 사이에 새로 생성된 노드를 추가해주도록 한다.
PNODE Prev = m_End->m_Prev;
// End노드의 이전노드의 다음노드를 End에서 새로 생성된 노드로 교체한다.
Prev->m_Next = Node;
// 새로 생성된 노드의 이전노드를 End의 이전노드로 지정한다.
Node->m_Prev = Prev;
// 새로 생성된 노드의 다음 노드를 End노드르 지정한다.
Node->m_Next = m_End;
// End노드의 이전노드를 새로 생성한 노드로 지정한다.
m_End->m_Prev = Node;
++m_Size;
}
void push_front(const T& data)
{
PNODE Node = new NODE;
Node->m_Data = data;
// Begin노드와 Begin노드의 다음노드 사이에 새로 생성된 노드를 추가한다.
PNODE Next = m_Begin->m_Next;
// 새로 생성된 노드의 다음노드로 Begin노드의 다음노드를 지정한다.
Node->m_Next = Next;
// Begin노드의 다음노드의 이전노드를 새로 생성된 노드로 지정한다.
Next->m_Prev = Node;
// Begin노드의 다음노드로 새로 생성된 노드를 지정한다.
m_Begin->m_Next = Node;
// 새로 생성된 노드의 이전노드로 Begin노드를 지정한다.
Node->m_Prev = m_Begin;
++m_Size;
}
// 마지막 노드를 제거한다.
void pop_back()
{
if (empty())
{
assert(false);
}
// End노드의 이전 노드를 지워준다. 그러므로 End의 이전노드를 얻어오고
// End의 이전 노드의 이전노드를 얻어와서 End와 연결해준다.
PNODE DeleteNode = m_End->m_Prev;
PNODE Prev = DeleteNode->m_Prev;
// 지울노드의 이전노드의 다음노드를 End노드로 지정한다.
Prev->m_Next = m_End;
// End노드의 이전노드로 지울노드의 이전노드를 지정한다.
m_End->m_Prev = Prev;
// 노드를 제거한다.
delete DeleteNode;
--m_Size;
}
void pop_front()
{
if (empty())
{
assert(false);
}
// Begin노드의 다음 노드를 지워준다.
PNODE DeleteNode = m_Begin->m_Next;
// 지울 노드의 다음 노드를 얻어온다.
PNODE Next = DeleteNode->m_Next;
// Begin노드의 다음노드로 지울 노드의 다음노드를 지정한다.
m_Begin->m_Next = Next;
// 지울노드의 다음노드의 이전노드로 Begin노드를 지정한다.
Next->m_Prev = m_Begin;
// 노드를 제거한다.
delete DeleteNode;
--m_Size;
}
const T& front() const
{
if (empty())
assert(false);
return m_Begin->m_Next->m_Data;
}
const T& back() const
{
if (empty())
assert(false);
return m_End->m_Prev->m_Data;
}
// 클래스의 멤버함수는 함수의 뒤에 const를 붙일 수 있다.
// 멤버함수의 뒤에 const가 붙으면 이 함수에서는 멤버변수의 값을 변경할 수 없게 된다.
// const 객체가 만들어진 경우라면 뒤에 const가 붙은 멤버함수만 호출이 가능하다.
int size() const
{
return m_Size;
}
bool empty() const
{
return m_Size == 0;
}
// 추가된 모든 노드를 제거한다.
void clear()
{
if (empty())
return;
PNODE DeleteNode = m_Begin->m_Next;
// End노드가 아니라면 while을 돌면서 제거해주도록 한다.
while (DeleteNode != m_End)
{
// 지울 노드의 다음노드를 미리 받아둬야 한다.
// 왜냐하면 노드를 먼저 지워버린다면 다음노드를 얻어올 수 없기 때문이다.
PNODE Next = DeleteNode->m_Next;
// 노드를 제거한다.
delete DeleteNode;
// 지울 노드를 다음 노드로 갱신한다.
DeleteNode = Next;
}
// Begin노드와 End노드를 서로 연결해준다.
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
// 노드의 개수를 0으로 초기화해준다.
m_Size = 0;
}
iterator begin() const
{
iterator iter;
iter.m_Node = m_Begin->m_Next;
return iter;
}
iterator end() const
{
iterator iter;
iter.m_Node = m_End;
return iter;
}
reverse_iterator rbegin() const
{
reverse_iterator iter;
iter.m_Node = m_End->m_Prev;
return iter;
}
reverse_iterator rend() const
{
reverse_iterator iter;
iter.m_Node = m_Begin;
return iter;
}
void operator = (const CList<T>& list)
{
// 기존 정보를 제거한다.
clear();
m_Size = list.m_Size;
PNODE Node = list.m_Begin->m_Next;
while (Node != list.m_End)
{
push_back(Node->m_Data);
Node = Node->m_Next;
}
}
// 삭제한 노드의 다음 노드를 가지고 있는 iterator를 반환해준다.
iterator erase(const T& data)
{
iterator iter = Find(data);
if (iter == end())
return iter;
return erase(iter);
}
iterator erase(const iterator& iter)
{
if (iter == end() || iter == m_Begin)
return end();
// 지울 노드의 이전노드와 다음노드를 얻어온다.
// 지울 노드의 이전노드와 다음노드를 서로 연결시켜준다.
PNODE Prev = iter.m_Node->m_Prev;
PNODE Next = iter.m_Node->m_Next;
Prev->m_Next = Next;
Next->m_Prev = Prev;
delete iter.m_Node;
--m_Size;
iterator result;
result.m_Node = Next;
return result;
}
iterator Find(const T& data)
{
iterator iter = begin();
iterator iterEnd = end();
for (; iter != iterEnd; ++iter)
{
if (*iter == data)
return iter;
}
// 못찾을 경우 end를 리턴한다.
return iterEnd;
}
// 정렬
void sort(bool (*pFunc)(const T&, const T&))
{
iterator iter1 = begin();
iterator iter1End = end();
--iter1End;
iterator iter2;
iterator iter2End = end();
// 예를 들어 10개라면 0 ~ 8 까지만 반복한다.
// 마지막 9번은 반복을 안한다.
for (; iter1 != iter1End; ++iter1)
{
iter2 = iter1;
++iter2;
for (; iter2 != iter2End; ++iter2)
{
if (pFunc(*iter1, *iter2))
{
T data = *iter1;
*iter1 = *iter2;
*iter2 = data;
}
}
}
}
};
// main.cpp
#define _CRTDBG_MAP_ALLOC
#include <iostream>
#include "Dijkstra.h"
#include <crtdbg.h>
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
//_CrtSetBreakAlloc(157);
CDijkstra<const char*> dijkstra;
dijkstra.insert("1");
dijkstra.insert("2");
dijkstra.insert("3");
dijkstra.insert("4");
dijkstra.insert("5");
dijkstra.insert("6");
dijkstra.insert("7");
dijkstra.AddEdge("1", "2", 2);
dijkstra.AddEdge("1", "6", 3);
dijkstra.AddEdge("2", "3", 20);
dijkstra.AddEdge("2", "5", 9);
dijkstra.AddEdge("3", "5", 6);
dijkstra.AddEdge("3", "4", 4);
dijkstra.AddEdge("4", "7", 1);
dijkstra.AddEdge("5", "6", 7);
dijkstra.AddEdge("5", "7", 6);
CList<const char*> FindList;
if (dijkstra.FindRoute("1", "4", &FindList))
{
CList<const char*>::iterator iter;
for (iter = FindList.begin(); iter != FindList.end(); ++iter)
{
std::cout << *iter << " -> ";
}
std::cout << std::endl;
}
else
{
std::cout << "길이 없다" << std::endl;
}
return 0;
}
이전에 구현했던 heap과, list를 이용해서 다익스트라 알고리즘을 구현해보았다.
최소힙을 이용한 다익스트라 알고리즘의 시간복잡도는 O(ElogV)이다(아래에 구현된 다익스트라를 보고 이해해보자).
- while문도 결국 for문안에 if에서 pq에 push하는 만큼 돌아가는데 push하는 횟수는 최대 E 만큼이므로 결국 O(E) 만큼 while문을 돌 것이다(for문안에 if문 조건을 만족하는 횟수만큼 while문을 반복하므로, nested loop 개념으로 두 반복문 반복 횟수를 단순히 곱하면 안된다).
- while문안에서 heap에서 top, push를 하는데 top은 O(1)에 얻을 수 있고, push/pop은 O(logE)만큼 걸린다.
- 2번의 동작을 while문을 돌면서 진행하면 O(E) * ( O(1) + O(logE) ) = O(E) + O(ElogE) = O(ElogE)가 된다.
reference에 적힌 블로그에 다른 분이 구현하신 다익스트라를 보면 시간복잡도가 훨씬 잘 와닿는다
int V;
// 그래프의 인접 리스트. (연결된 정점 번호, 간선 가중치) 쌍을 담는다.
vector<pair<int, int> > adj[MAX_V];
vector<int> dijkstra(int src) {
vector<int> dist(V, INF);
dist[src] = 0;
priority_queue<pair<int, int> > pq;
pq.push(make_pair(0, src));
while (!pq.empty()) {
int cost = -pq.top().first;
int here = pq.top().second;
pq.pop();
// 만약 지금 꺼낸 것보다 더 짧은 경로를 알고 있다면 지금 꺼낸 것을 무시한다.
if (dist[here] < cost) continue;
// 인접한 정점들을 모두 검사한다.
for (int i = 0; i < adj[here].size(); ++i) {
int there = adj[here][i].first;
int nextDist = cost + adj[here][i].second;
// 더 짧은 경로를 발견하면, dist[]를 갱신하고 우선순위 큐에 넣는다.
if (dist[there] > nextDist) {
dist[there] = nextDist;
pq.push(make_pair(-nextDist, there));
}
}
}
return dist;
}
Reference
https://sungjk.github.io/2016/05/13/Dijkstra.html
'공부 > Algorithm' 카테고리의 다른 글
JPS(Jump Point Search) Algorithm (0) | 2022.02.17 |
---|---|
병합 정렬(Merge sort) (0) | 2021.08.10 |
DFS(Depth First Search) & BFS(Breadth First Search) (0) | 2021.08.10 |
마스터 정리(Master Method), Big-Oh, Theta, Omega Notation (0) | 2021.08.07 |
힙 정렬 (Heap Sort) 구현 (0) | 2021.08.07 |