-
인접 리스트를 통한 그래프 구현프로그래밍 기초/자료구조 2021. 5. 3. 09:37
인접 리스트 통한 그래프 구현
그래프 구현/인접 리스트
인접 리스트는 각각의 노드를 하나의 포인터로 하여 특정 노드에서 이동 가능한 노드를 표현하는 방법으로 다음과 같이 표현된다.
구현이 필요한 메서드 및 속성은 다음과 같다.
- 생성자
- ArrayListGraph() 비어있는는 인스턴스 생성
- 속성
- NodeCount 현재 노드의 개수
- 메서드
- InsertNode() 노드 추가
- InsertEdge(int, int) 앞의 노드에서 뒷 노드로 이동하는 에지 추가
- RemoveNode(int) 지정된 인덱스의 노드 제거
- RemoveEdge(int, int) 앞의 노드에서 뒷 노드로 이동하는 제거
- Clear() 저장되어 있는 모든 데이터 삭제
- GetDegreeIn(int) 노드의 진입 차수 반환
- GetDegreeOut(int) 노드의 진출 차수 반환
구현 코드
ArrayListGraph.h
#pragma once #include <iostream> struct GraphNode { GraphNode(int value) : m_idx(value), m_next(nullptr) { } int m_idx; GraphNode* m_next; }; struct GraphHeader { GraphHeader(int value) : m_idx(value), m_data(nullptr), m_next(nullptr) { } int m_idx; GraphNode* m_data; GraphHeader* m_next; }; class ArrayListGraph { public: #pragma region 생성자 ArrayListGraph(); ~ArrayListGraph(); #pragma endregion #pragma region 속성 size_t NodeCount() { return m_nodeCount; } #pragma endregion #pragma region 메서드 void InsertNode(int num); void InsertEdge(int from, int to); void RemoveNode(int num); void RemoveEdge(int from, int to); void Clear(); size_t GetDegreeIn(int num); size_t GetDegreeOut(int num); bool ContainsNode(int num); bool ContainsEdge(int from, int to); GraphHeader* GetTargetHeader(int num); void PrintInfo(); #pragma endregion private: #pragma region Class Util GraphHeader* PopHeader(int value); void PushHeader(GraphHeader* header); GraphNode* PopNode(int value); void PushNode(GraphNode* node); #pragma endregion #pragma region 변수 size_t m_nodeCount; GraphHeader* m_graphHeaders; GraphHeader* m_freeHeaders; GraphNode* m_freeNodes; #pragma endregion };
ArrayListGraph.cpp
#include "ArrayListGraph.h" #pragma region 생성자 /// <summary> /// 지정된 크기 혹은 기본 크기의 ArrayListGraph를 생성한다. /// </summary> ArrayListGraph::ArrayListGraph() : m_nodeCount(0), m_graphHeaders(nullptr), m_freeHeaders(nullptr), m_freeNodes(nullptr) { } /// <summary> /// 메모리 누수를 막기 위해 동적 생성한 노드들을 제거한다. /// </summary> ArrayListGraph::~ArrayListGraph() { while (m_freeNodes != nullptr) { GraphNode* curNode{ m_freeNodes }; m_freeNodes = m_freeNodes->m_next; delete curNode; } while (m_graphHeaders != nullptr) { GraphHeader* curHead{ m_graphHeaders }; m_graphHeaders = m_graphHeaders->m_next; while (curHead->m_data != nullptr) { GraphNode* curNode{ curHead->m_data }; curHead->m_data = curNode->m_next; delete curNode; } delete curHead; } while (m_freeHeaders != nullptr) { GraphHeader* curHead{ m_freeHeaders }; m_freeHeaders = m_freeHeaders->m_next; delete curHead; } } #pragma endregion #pragma region 메서드 /// <summary> /// ArrayListGraph에 노드를 추가한다. /// </summary> void ArrayListGraph::InsertNode(int num) { if (ContainsNode(num)) { return; } GraphHeader* newHeader{ PopHeader(num) }; newHeader->m_next = m_graphHeaders; m_graphHeaders = newHeader; m_nodeCount++; } /// <summary> /// ArrayListGraph에 from -> to 인 간선을 추가한다. /// </summary> /// <param name="from">시작 노드</param> /// <param name="to">끝 노드</param> void ArrayListGraph::InsertEdge(int from, int to) { if (ContainsEdge(from, to)) { return; } GraphHeader* fromHeader{ GetTargetHeader(from) }; GraphNode* newNode{ PopNode(to) }; newNode->m_next = fromHeader->m_data; fromHeader->m_data = newNode; } /// <summary> /// ArrayListGraph에서 지정된 인덱스의 노드를 제거한다. /// </summary> /// <param name="value">제거할 값</param> void ArrayListGraph::RemoveNode(int num) { if (!ContainsNode(num)) { std::cout << "존재하지 않는 노드를 제거하려 하고 있습니다.\n"; return; } GraphHeader* curHeader{ m_graphHeaders }; while (curHeader != nullptr) { if (ContainsEdge(curHeader->m_idx, num)) { RemoveEdge(curHeader->m_idx, num); } curHeader = curHeader->m_next; } GraphHeader* prevHeader{ nullptr }; curHeader = m_graphHeaders; while (curHeader != nullptr) { if (curHeader->m_idx == num) { break; } prevHeader = curHeader; curHeader = curHeader->m_next; } if (prevHeader == nullptr) { m_graphHeaders = curHeader->m_next; } else { prevHeader->m_next = curHeader->m_next; } PushHeader(curHeader); m_nodeCount--; } /// <summary> /// ArrayListGraph에서 from -> to 인 간선을 제거한다. /// </summary> /// <param name="from">시작 노드</param> /// <param name="to">끝 노드</param> void ArrayListGraph::RemoveEdge(int from, int to) { if (!ContainsNode(from) || !ContainsNode(to)) { std::cout << "존재하지 않는 에지를 제거하려 하고 있습니다.\n"; return; } GraphHeader* targetHeader{ GetTargetHeader(from) }; GraphNode* prevNode{ nullptr }; GraphNode* curNode{ targetHeader->m_data }; while (curNode != nullptr) { if (curNode->m_idx == to) { break; } prevNode = curNode; curNode = curNode->m_next; } if (prevNode == nullptr) { targetHeader->m_data = curNode->m_next; } else { prevNode->m_next = curNode->m_next; } PushNode(curNode); } /// <summary> /// ArrayListGraph의 모든 노드 및 간선을 제거한다. /// </summary> void ArrayListGraph::Clear() { while (m_graphHeaders != nullptr) { GraphHeader* curHead{ m_graphHeaders }; m_graphHeaders = m_graphHeaders->m_next; PushHeader(curHead); } m_nodeCount = 0; } /// <summary> /// 지정된 인덱스의 노드에 진입하는 차수를 반환한다. /// </summary> /// <param name="num">노드의 번호</param> size_t ArrayListGraph::GetDegreeIn(int num) { if (!ContainsNode(num)) { return -1; } int count{ 0 }; GraphHeader* curHead{ m_graphHeaders }; while (curHead != nullptr) { GraphNode* curNode{ curHead->m_data }; while (curNode != nullptr) { if (curNode->m_idx == num) { count++; } curNode = curNode->m_next; } curHead = curHead->m_next; } return count; } /// <summary> /// 지정된 인덱스의 노드에서 진출하는 차수를 반환한다. /// </summary> /// <param name="num">노드의 번호</param> size_t ArrayListGraph::GetDegreeOut(int num) { GraphHeader* targetHeader{ GetTargetHeader(num) }; if (targetHeader == nullptr) { return -1; } int count{ 0 }; GraphNode* targetNode = targetHeader->m_data; while (targetNode != nullptr) { targetNode = targetNode->m_next; count++; } return count; } /// <summary> /// 그래프에 지정된 숫자의 노드가 존재하는지 검사 /// </summary> /// <param name="num">확인할 번호</param> /// <returns>노드 존재 여부</returns> bool ArrayListGraph::ContainsNode(int num) { GraphHeader* curHead{ m_graphHeaders }; while (curHead != nullptr) { if (curHead->m_idx == num) { return true; } curHead = curHead->m_next; } return false; } /// <summary> /// 그래프에 시작 노드에서 끝 노드로의 에지가 존재하는지 검사 /// </summary> /// <param name="from">시작 노드 번호</param> /// <param name="to">끝 노드 번호</param> /// <returns>에지 존재 여부</returns> bool ArrayListGraph::ContainsEdge(int from, int to) { if (!ContainsNode(from) || !ContainsNode(to)) { std::cout << "존재하지 않는 노드를 추가하려 하고 있습니다.\n"; return true; } GraphHeader* targetHeader{ GetTargetHeader(from) }; GraphNode* targetNode{ targetHeader->m_data }; while (targetNode != nullptr) { if (targetNode->m_idx == to) { return true; } targetNode = targetNode->m_next; } return false; } /// <summary> /// 지정된 번호의 헤더가 존재하는 경우 헤더 반환 /// </summary> /// <param name="num">노드 번호</param> /// <returns>해당 번호의 노드(없으면 nullptr)</returns> GraphHeader* ArrayListGraph::GetTargetHeader(int num) { GraphHeader* target{ m_graphHeaders }; while (target != nullptr) { if (target->m_idx == num) { break; } target = target->m_next; } return target; } /// <summary> /// 테스트용 리스트 정보 출력 함수 /// </summary> void ArrayListGraph::PrintInfo() { std::cout << "----------------------\n"; std::cout << "Count: " << m_nodeCount << '\n'; GraphHeader* curHeader{ m_graphHeaders }; while (curHeader != nullptr) { std::cout << curHeader->m_idx << " : "; if (curHeader->m_data == nullptr) { std::cout << "NULL\n"; } else { GraphNode* curNode{ curHeader->m_data }; while (curNode != nullptr) { std::cout << curNode->m_idx << ' '; curNode = curNode->m_next; } std::cout << '\n'; } curHeader = curHeader->m_next; } std::cout << "----------------------\n\n"; } #pragma endregion #pragma region Class Util /// <summary> /// 자유 공간 리스트에서 헤더를 가져오거나 새로 생성한다. /// </summary> /// <param name="value">헤더 생성 시 인덱스 값</param> /// <returns>새 헤더</returns> GraphHeader* ArrayListGraph::PopHeader(int value) { GraphHeader* newNode{ nullptr }; if (m_freeHeaders == nullptr) { newNode = new GraphHeader(value); } else { newNode = m_freeHeaders; m_freeHeaders = m_freeHeaders->m_next; newNode->m_idx = value; newNode->m_data = nullptr; newNode->m_next = nullptr; } return newNode; } /// <summary> /// 자유 공간 리스트에 제거된 헤더를 저장한다. /// </summary> /// <param name="node">제거된 헤더</param> void ArrayListGraph::PushHeader(GraphHeader* header) { while (header->m_data != nullptr) { GraphNode* curNode{ header->m_data }; header->m_data = curNode->m_next; PushNode(curNode); } header->m_next = m_freeHeaders; m_freeHeaders = header; } /// <summary> /// 자유 공간 리스트에서 노드를 가져오거나 새로 생성한다. /// </summary> /// <param name="value">노드 생성시 초기값</param> /// <returns>새 노드</returns> GraphNode* ArrayListGraph::PopNode(int value) { GraphNode* newNode{ nullptr }; if (m_freeNodes == nullptr) { newNode = new GraphNode(value); } else { newNode = m_freeNodes; m_freeNodes = m_freeNodes->m_next; newNode->m_idx = value; newNode->m_next = nullptr; } return newNode; } /// <summary> /// 자유 공간 리스트에 제거된 노드를 저장한다. /// </summary> /// <param name="node">제거된 노드</param> void ArrayListGraph::PushNode(GraphNode* node) { node->m_next = m_freeNodes; m_freeNodes = node; } #pragma endregion
테스트 코드
main.cpp
#include <iostream> #include "Graph/ArrayListGraph.h" // 생성한 자료구조 테스트용 메인 int main() { ArrayListGraph graph; graph.InsertNode(1); graph.InsertNode(2); graph.InsertNode(3); graph.InsertNode(4); graph.InsertEdge(1, 2); graph.InsertEdge(1, 4); graph.InsertEdge(2, 3); graph.InsertEdge(2, 4); graph.InsertEdge(3, 4); graph.PrintInfo(); std::cout << graph.GetDegreeIn(1) << ' ' << graph.GetDegreeOut(1) << '\n'; graph.RemoveEdge(1, 4); graph.PrintInfo(); std::cout << graph.GetDegreeIn(1) << ' ' << graph.GetDegreeOut(1) << '\n'; graph.RemoveNode(2); graph.PrintInfo(); }
실행 결과
---------------------- Count: 4 4 : NULL 3 : 4 2 : 4 3 1 : 4 2 ---------------------- 0 2 ---------------------- Count: 4 4 : NULL 3 : 4 2 : 4 3 1 : 2 ---------------------- 0 1 ---------------------- Count: 3 4 : NULL 3 : 4 1 : NULL ----------------------
- 생성자