공부/Algorithm

DFS(Depth First Search) & BFS(Breadth First Search)

sudo 2021. 8. 10. 05:17

 

// Graph.h
#pragma once

#include "Stack.h"
#include "Queue.h"
#include <assert.h>

template <typename T>
class CEdge
{
	template <typename T>
	friend class CGraph;

	template <typename T>
	friend class CGraphNode;

private:
	CEdge()
	{
		m_Node = nullptr;
	}

	~CEdge()
	{
	}

private:
	class CGraphNode<T>* m_Node;
};

template <typename T>
class CGraphNode
{
	template <typename T>
	friend class CGraph;

private:
	CGraphNode()
	{
		m_Size = 0;
		m_Capacity = 1;
		m_EdgeArray = new CEdge<T>*[m_Capacity];
		m_Visit = false;
	}

	~CGraphNode()
	{
		for (int i = 0; i < m_Size; ++i)
		{
			delete	m_EdgeArray[i];
		}

		delete[]	m_EdgeArray;
	}

private:
	CEdge<T>** m_EdgeArray;
	int	m_Size;
	int	m_Capacity;
	T		m_Data;
	bool	m_Visit;

private:
	void AddEdge(CGraphNode<T>* Node)
	{
		if (m_Size == m_Capacity)
		{
			m_Capacity *= 2;

			CEdge<T>** Array = new CEdge<T>*[m_Capacity];

			memcpy(Array, m_EdgeArray, sizeof(CEdge<T>*) * m_Size);

			delete[]	m_EdgeArray;

			m_EdgeArray = Array;
		}

		CEdge<T>* Edge = new CEdge<T>;
		Edge->m_Node = Node;

		m_EdgeArray[m_Size] = Edge;
		++m_Size;
	}

};

template <typename T>
class CGraph
{
public:
	CGraph()
	{
		m_Size = 0;
		m_Capacity = 4;

		m_NodeArray = new CGraphNode<T>*[m_Capacity];
	}

	~CGraph()
	{
		for (int i = 0; i < m_Size; ++i)
		{
			delete	m_NodeArray[i];
		}

		delete[]	m_NodeArray;
	}

private:
	CGraphNode<T>** m_NodeArray;
	int		m_Size;
	int		m_Capacity;
	CStack<CGraphNode<T>*> m_Stack;
	CQueue<CGraphNode<T>*> m_Queue;

public:
	void insert(const T& data)
	{
		if (m_Size == m_Capacity)
		{
			m_Capacity *= 2;

			CGraphNode<T>** Array = new CGraphNode<T>*[m_Capacity];

			memcpy(Array, m_NodeArray, sizeof(CGraphNode<T>*) * m_Size);

			delete[]	m_NodeArray;

			m_NodeArray = Array;
		}

		CGraphNode<T>* Node = new CGraphNode<T>;

		Node->m_Data = data;

		m_NodeArray[m_Size] = Node;
		++m_Size;
	}

	// 노드에 edge를 추가해준다.
	void AddEdge(const T& Src, const T& Dest)
	{
		CGraphNode<T>* SrcNode = nullptr;
		CGraphNode<T>* DestNode = nullptr;

		for (int i = 0; i < m_Size; ++i)
		{
			if (m_NodeArray[i]->m_Data == Src)
				SrcNode = m_NodeArray[i];

			else if (m_NodeArray[i]->m_Data == Dest)
				DestNode = m_NodeArray[i];

			if (SrcNode && DestNode)
				break;
		}

		if (!SrcNode || !DestNode)
			return;

		SrcNode->AddEdge(DestNode);
		DestNode->AddEdge(SrcNode);
	}

	// data를 갖고 있는 노드를 찾아서 반환하는 함수
	CGraphNode<T>* FindNode(const T& Data)
	{
		CGraphNode<T>* Node = nullptr;

		for (int i = 0; i < m_Size; ++i)
		{
			if (m_NodeArray[i]->m_Data == Data)
			{
				Node = m_NodeArray[i];
				return Node;
			}
		}

		if (!Node)
		{
			assert(Node != nullptr);
		}
	}

	void DFS()
	{
		CGraphNode<T>* Start = m_NodeArray[0];

		if (Start->m_Visit == false)
		{
			m_Stack.push(Start);
			Start->m_Visit = true;
		}

		CGraphNode<T>* Node = nullptr;

		while (!m_Stack.empty())
		{
			Node = m_Stack.top();
			m_Stack.pop();

			VisitOutput(Node);

			// 인접 노드중에 아직 스택에 안들어 간 것 중에 가장 작은것 부터 탐색하고 싶어서
			// m_EdgeArray안의 노드들의 값 기준으로 내림차순 정렬 => 스택에서는 작은값 부터 top에 위치
			for (int i = 0; i < Node->m_Size - 1; ++i)
			{
				for (int j = i + 1; j < Node->m_Size; ++j)
				{
					T DataTmp1 = Node->m_EdgeArray[i]->m_Node->m_Data;
					T DataTmp2 = Node->m_EdgeArray[j]->m_Node->m_Data;
					if (DataTmp1 < DataTmp2)
					{
						CEdge<T>* EdgeTmp = Node->m_EdgeArray[i];
						Node->m_EdgeArray[i] = Node->m_EdgeArray[j];
						Node->m_EdgeArray[j] = EdgeTmp;
					}
				}
			}

			for (int i = 0; i < Node->m_Size; ++i)
			{
				CGraphNode<T>* TmpNode = Node->m_EdgeArray[i]->m_Node;
				
				if (TmpNode->m_Visit == false)
				{
					m_Stack.push(TmpNode);
					TmpNode->m_Visit = true;
				}
			}
		}
	}

	void BFS()
	{
		CGraphNode<T>* Start = m_NodeArray[0];

		if (Start->m_Visit == false)
		{
			m_Queue.push(Start);
			Start->m_Visit = true;
		}

		CGraphNode<T>* Node = nullptr;

		while (!m_Queue.empty())
		{
			Node = m_Queue.front();
			m_Queue.pop();

			VisitOutput(Node);

			// 인접 노드중에 아직 큐에 안들어 간 것 중에 가장 작은것 부터 탐색하고 싶어서
			// m_EdgeArray안의 노드들의 값 기준으로 오름차순 정렬 => 큐에서는 작은값 부터 front에 위치
			for (int i = 0; i < Node->m_Size - 1; ++i)
			{
				for (int j = i + 1; j < Node->m_Size; ++j)
				{
					T DataTmp1 = Node->m_EdgeArray[i]->m_Node->m_Data;
					T DataTmp2 = Node->m_EdgeArray[j]->m_Node->m_Data;
					if (DataTmp1 > DataTmp2)
					{
						CEdge<T>* EdgeTmp = Node->m_EdgeArray[i];
						Node->m_EdgeArray[i] = Node->m_EdgeArray[j];
						Node->m_EdgeArray[j] = EdgeTmp;
					}
				}
			}

			for (int i = 0; i < Node->m_Size; ++i)
			{
				CGraphNode<T>* TmpNode = Node->m_EdgeArray[i]->m_Node;

				if (TmpNode->m_Visit == false)
				{
					m_Queue.push(TmpNode);
					TmpNode->m_Visit = true;
				}
			}
		}
	}

	// 그래프 내 모든 노드의 m_Visit = false로 다시 복귀
	void ResetVisit()
	{
		for (int i = 0; i < m_Size; ++i)
		{
			m_NodeArray[i]->m_Visit = false;
		}
	}

	void DFSRecursive()
	{
		DFSRecursive(m_NodeArray[0]);
	}

private:
	void VisitOutput(CGraphNode<T>* Node)
	{
		std::cout << "Visit Node : " << Node->m_Data << std::endl;
	}

	void DFSRecursive(CGraphNode<T>* Node)
	{
		if (Node->m_Visit)
		{
			return;
		}

		Node->m_Visit = true;
		VisitOutput(Node);

		// 인접한 노드를 오름차순으로 정렬해서 재귀호출 -> 인접한 노드중에서 낮은 값의 노드 먼저 탐색
		for (int i = 0; i < Node->m_Size - 1; ++i)
		{
			for (int j = i + 1; j < Node->m_Size; ++j)
			{
				T DataTmp1 = Node->m_EdgeArray[i]->m_Node->m_Data;
				T DataTmp2 = Node->m_EdgeArray[j]->m_Node->m_Data;
				if (DataTmp1 > DataTmp2)
				{
					CEdge<T>* EdgeTmp = Node->m_EdgeArray[i];
					Node->m_EdgeArray[i] = Node->m_EdgeArray[j];
					Node->m_EdgeArray[j] = EdgeTmp;
				}
			}
		}

		for (int i = 0; i < Node->m_Size; ++i)
		{
			CGraphNode<T>* NextNode = Node->m_EdgeArray[i]->m_Node;
			DFSRecursive(NextNode);
		}
	}
};

 

// Queue.h
#pragma once

#include <assert.h>

template <typename T>
class CQueueNode
{
	template <typename T>
	friend class CQueue;

private:
	CQueueNode() :
		m_Next(nullptr)
	{
	}

	~CQueueNode()
	{
	}

private:
	CQueueNode<T>* m_Next;
	T		m_Data;
};

template <typename T>
class CQueue
{
public:
	CQueue()
	{
		m_FirstNode = nullptr;
		m_LastNode = nullptr;
		m_Size = 0;
	}

	~CQueue()
	{
		clear();
	}

private:
	CQueueNode<T>* m_FirstNode;
	CQueueNode<T>* m_LastNode;
	int		m_Size;

public:
	void push(const T& data)
	{
		CQueueNode<T>* Node = new CQueueNode<T>;

		Node->m_Data = data;

		// 기존에 추가된 가장 마지막노드의 다음노드로 새로 생성된 노드를 지정한다.
		if (m_LastNode)
			m_LastNode->m_Next = Node;

		// 만약 처음 추가되는 노드라면 FirstNode를 새로 생성된 노드로 채워준다.
		if (!m_FirstNode)
			m_FirstNode = Node;

		// 가장 마지막 노드를 새로 생성된 노드로 갱신한다.
		m_LastNode = Node;

		++m_Size;
	}

	T& front()	const
	{
		if (empty())
			assert(false);

		return m_FirstNode->m_Data;
	}

	void pop()
	{
		if (empty())
			assert(false);

		CQueueNode<T>* Next = m_FirstNode->m_Next;

		delete	m_FirstNode;

		m_FirstNode = Next;

		if (!m_FirstNode)
			m_LastNode = nullptr;

		--m_Size;
	}

	int size()	const
	{
		return m_Size;
	}

	bool empty()	const
	{
		return m_Size == 0;
	}

	void clear()
	{
		while (m_FirstNode)
		{
			CQueueNode<T>* Next = m_FirstNode->m_Next;
			delete	m_FirstNode;

			m_FirstNode = Next;
		}

		m_LastNode = nullptr;
		m_Size = 0;
	}
};

 

// Stack.h
#pragma once

#include <assert.h>

template <typename T>
class CStackNode
{
	template <typename T>
	friend class CStack;

private:
	CStackNode() :
		m_Next(nullptr)
	{
	}

	~CStackNode()
	{
	}

private:
	CStackNode<T>* m_Next;
	T				m_Data;
};

template <typename T>
class CStack
{
public:
	CStack()
	{
		m_LastNode = nullptr;
		m_Size = 0;
	}

	~CStack()
	{
		clear();
	}

private:
	// 가장 마지막에 추가된 노드의 주소를 담는다.
	CStackNode<T>* m_LastNode;
	int				m_Size;

public:
	void push(const T& data)
	{
		// 데이터를 저장하기 위한 노드를 생성한다.
		CStackNode<T>* Node = new CStackNode<T>;

		Node->m_Data = data;

		// 새로 생성된 노드의 다음노드를 기존에 마지막에 추가된 노드로 지정해준다.
		// 만약 처음 추가하는 노드라면 m_LastNode는 nullptr이 들어가 있으므로
		// 새로생성된 노드의 다음으로는 nullptr이 지정될 것이다.
		// 즉, 다음 노드가 nullptr이라면 더이상 노드가 없다는 의미이다.
		Node->m_Next = m_LastNode;

		// 마지막으로 추가된 노드를 갱신해주도록 한다.
		m_LastNode = Node;

		++m_Size;
	}

	T& top()	const
	{
		if (empty())
			assert(false);

		return m_LastNode->m_Data;
	}

	void pop()
	{
		if (empty())
			assert(false);

		CStackNode<T>* Next = m_LastNode->m_Next;

		delete	m_LastNode;

		m_LastNode = Next;

		--m_Size;
	}

	int size()	const
	{
		return m_Size;
	}

	bool empty()	const
	{
		return m_Size == 0;
	}

	void clear()
	{
		while (m_LastNode)
		{
			CStackNode<T>* Next = m_LastNode->m_Next;
			delete	m_LastNode;
			m_LastNode = Next;
		}

		m_Size = 0;
	}
};

 

// main.cpp
#include <iostream>
#include "Graph.h"

int main()
{
	CGraph<int> graph;

	for (int i = 1; i < 10; ++i)
	{
		graph.insert(i);
	}

	// 각 노드들의 m_EdgeArray가 연결된 노드의 값 기준으로
	// 정렬되어 있지 않도록 해도 작은값 순서대로 탐색하는지 보기

	graph.AddEdge(7, 8);
	graph.AddEdge(1, 7);
	graph.AddEdge(2, 3);
	graph.AddEdge(1, 2);
	graph.AddEdge(1, 5);
	graph.AddEdge(3, 4);
	graph.AddEdge(5, 6);
	graph.AddEdge(8, 9);


	std::cout << "========= DFS =========" << std::endl;

	graph.DFS();

	graph.ResetVisit();

	std::cout << "========= DFS Recursive=========" << std::endl;

	graph.DFSRecursive();

	graph.ResetVisit();
	
	std::cout << "========= BFS =========" << std::endl;

	graph.BFS();

	return 0;
}

 

 

stack과 queue는 STL을 사용하지 않고 직접 구현했던 것을 사용해봤다. 한가지 눈에 띄는 점은 같은 DFS라도 스택을 이용해서 구현하느냐, 재귀 함수를 이용해서 구현하느냐에 따라 탐색 순서가 달라졌다(물론 두 순서 모두 유효한 탐색 순서로 탐색한다).