-
연결 자료구조 - 원형 연결 리스트(Circular Linked List)프로그래밍 기초/자료구조 2020. 11. 11. 08:45
원형 연결 리스트
원형 연결 리스트는 마지막 노드가 처음 노드를 가리키는 환형 구조가 되는 연결 리스트로 단일 연결 리스트와 이중 연결 리스트에 모두 적용이 가능하다.
- 단일 연결 리스트 적용 예
- 이중 연결 리스트 적용 예
※ 리스트 순회 시 다음 노드가 첫 노드인 경우 순회를 종료하면 된다.
원형 연결 리스트 구현 준비
원형 연결 리스트는 단일 연결 리스트 혹은 이중 연결 리스트의 삽입과 삭제, 기능 연산에서 리스트의 끝을 확인하는 방식을 변경하여 구현할 수 있다.
단일 연결 리스트 혹은 이중 연결 리스트를 원형 연결 리스트로 변경하기 위해 다음의 메서드를 수정해야 한다.
- 생성자
- 복사 생성자
- 메서드
- 데이터 삽입 메서드
- 데이터 삭제 메서드
- 기능 메서드
단일 원형 연결 리스트 구현
SinglyLinkedList를 변형하여 단일 원형 연결 리스트를 구현한다.
SinglyCircularLinkedList.h
#pragma once #include <xutility> #include <iostream> struct SinglyCircularLinkedListNode { SinglyCircularLinkedListNode() {} SinglyCircularLinkedListNode(int value) { m_data = value; } int m_data{ 0 }; SinglyCircularLinkedListNode* m_next{ nullptr }; }; class SinglyCircularLinkedList { public: #pragma region 생성자 SinglyCircularLinkedList(); SinglyCircularLinkedList(const SinglyCircularLinkedList& other); ~SinglyCircularLinkedList(); #pragma endregion #pragma region 속성 const size_t Count() { return m_count; } SinglyCircularLinkedListNode& Front() { return *m_head; } #pragma endregion #pragma region 메서드 void Add(int value); void Add(SinglyCircularLinkedListNode* node); void Insert(size_t index, int value); void Insert(size_t index, SinglyCircularLinkedListNode* node); bool Remove(int value); void Remove(const SinglyCircularLinkedListNode* node); void Clear(); bool Contains(int value); bool Contains(const SinglyCircularLinkedListNode* node); SinglyCircularLinkedListNode* Find(int value); SinglyCircularLinkedListNode* FindLast(int value); #pragma endregion private: #pragma region Class Util SinglyCircularLinkedListNode* PopNode(int value); void PushNode(SinglyCircularLinkedListNode* node); void UpdateHead(SinglyCircularLinkedListNode* node); #pragma endregion private: #pragma region 변수 size_t m_count; SinglyCircularLinkedListNode* m_head; SinglyCircularLinkedListNode* m_free; #pragma endregion };
생성자, 소멸자 구현
기본 생성자와 SinglyCircularLinkedList를 인자로 받는 복사 생성자를 구현한다.
SinglyCircularLinkedList.cpp
/// <summary> /// 비어있는 SinglyCircularLinkedList를 생성한다. /// </summary> SinglyCircularLinkedList::SinglyCircularLinkedList() : m_count(0), m_head(nullptr), m_free(nullptr) { } /// <summary> /// 다른 SinglyCircularLinkedList가 가지고 있는 노드를 복사해 SinglyCircularLinkedList를 생성한다. /// </summary> /// <param name="other">기준이 될 SinglyCircularLinkedList</param> SinglyCircularLinkedList::SinglyCircularLinkedList(const SinglyCircularLinkedList& other) : m_count(other.m_count), m_head(nullptr), m_free(nullptr) { if (other.m_head == nullptr) { return; } SinglyCircularLinkedListNode* curNode{ other.m_head }; SinglyCircularLinkedListNode* prevNode{ PopNode(curNode->m_data) }; m_head = prevNode; curNode = curNode->m_next; while (curNode->m_next != other.m_head) { SinglyCircularLinkedListNode* newNode{ PopNode(curNode->m_data) }; prevNode->m_next = newNode; prevNode = newNode; curNode = curNode->m_next; } } /// <summary> /// 메모리 누수를 막기 위해 동적 생성한 노드들을 제거한다. /// </summary> SinglyCircularLinkedList::~SinglyCircularLinkedList() { if (m_head != nullptr) { SinglyCircularLinkedListNode* head{ m_head }; do { SinglyCircularLinkedListNode* curNode{ m_head }; m_head = m_head->m_next; delete curNode; } while (m_head != head); } while (m_free != nullptr) { SinglyCircularLinkedListNode* curNode{ m_free }; m_free = m_free->m_next; delete curNode; } }
데이터 삽입 메서드 구현
데이터 삽입 연산에 필요한 메서드들을 구현한다.
SinglyCircularLinkedList.cpp
/// <summary> /// SinglyCircularLinkedList의 시작 위치에 지정한 값이 포함된 새 노드를 추가한다. /// </summary> /// <param name="value">추가할 값</param> void SinglyCircularLinkedList::Add(int value) { SinglyCircularLinkedListNode* newNode{ PopNode(value) }; Add(newNode); } /// <summary> /// SinglyCircularLinkedList의 시작 위치에 지정한 노드를 추가한다. /// </summary> /// <param name="node">추가할 새 노드</param> void SinglyCircularLinkedList::Add(SinglyCircularLinkedListNode* node) { if (node == nullptr || node->m_next != nullptr) { throw std::invalid_argument("node"); } node->m_next = m_head; UpdateHead(node); m_count++; } /// <summary> /// SinglyCircularLinkedList의 지정한 인덱스에 해당하는 위치에 지정한 값이 포함된 새 노드를 추가한다. /// </summary> /// <param name="index">값을 추가할 인덱스</param> /// <param name="value">추가할 값</param> void SinglyCircularLinkedList::Insert(size_t index, int value) { SinglyCircularLinkedListNode* newNode{ PopNode(value) }; Insert(index, newNode); } /// <summary> /// SinglyCircularLinkedList의 지정한 인덱스에 해당하는 위치에 지정된 노드를 추가한다. /// </summary> /// <param name="index">새 노드를 추가할 인덱스</param> /// <param name="node">추가할 새 노드</param> void SinglyCircularLinkedList::Insert(size_t index, SinglyCircularLinkedListNode* node) { if (index > m_count) { throw std::out_of_range("index"); } if (node == nullptr || node->m_next != nullptr) { throw std::invalid_argument("node"); } SinglyCircularLinkedListNode* prevNode{ nullptr }; SinglyCircularLinkedListNode* curNode{ m_head }; while (index--) { prevNode = curNode; curNode = curNode->m_next; } node->m_next = curNode; if (prevNode != nullptr) { prevNode->m_next = node; } else { UpdateHead(node); } m_count++; }
데이터 삭제 메서드 구현
데이터 삭제 연산에 필요한 메서드들을 구현한다.
SinglyCircularLinkedList.cpp
/// <summary> /// SinglyCircularLinkedList에서 가장 처음 일치한 지정된 값을 포함한 노드를 제거한다. /// </summary> /// <param name="value">제거할 값</param> bool SinglyCircularLinkedList::Remove(int value) { if (m_head != nullptr) { SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { Remove(curNode); return true; } curNode = curNode->m_next; } while (curNode != m_head); } return false; } /// <summary> /// SinglyCircularLinkedList에서 지정된 노드를 제거한다. /// </summary> /// <param name="node">제거할 노드</param> void SinglyCircularLinkedList::Remove(const SinglyCircularLinkedListNode* node) { if (node == nullptr) { throw std::invalid_argument("node"); } if (m_head != nullptr) { SinglyCircularLinkedListNode* prevNode{ nullptr }; SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode == node) { if (prevNode != nullptr) { prevNode->m_next = curNode->m_next; } else { UpdateHead(curNode->m_next); } PushNode(curNode); m_count--; return; } prevNode = curNode; curNode = curNode->m_next; } while (curNode != m_head); } throw std::out_of_range("node"); } /// <summary> /// SinglyCircularLinkedList의 모든 노드를 제거한다. /// </summary> void SinglyCircularLinkedList::Clear() { if (m_head != nullptr) { SinglyCircularLinkedListNode* head{ m_head }; do { SinglyCircularLinkedListNode* curNode{ m_head }; m_head = m_head->m_next; PushNode(curNode); } while (m_head != head); m_head = nullptr; } m_count = 0; }
기능 메서드 구현
자료구조를 효율적으로 사용하기 위한 기능 메서드를 구현한다.
SinglyCircularLinkedList.cpp
/// <summary> /// 지정한 값을 포함한 노드가 존재하는지 확인한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>값의 존재 여부</returns> bool SinglyCircularLinkedList::Contains(int value) { if (m_head != nullptr) { SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { return true; } curNode = curNode->m_next; } while (curNode != m_head); } return false; } /// <summary> /// 지정한 노드가 SinglyCircularLinkedList에 포함되는지 확인한다. /// </summary> /// <param name="node">찾을 노드</param> /// <returns>노드의 포함 여부</returns> bool SinglyCircularLinkedList::Contains(const SinglyCircularLinkedListNode* node) { if (m_head != nullptr) { SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode == node) { return true; } curNode = curNode->m_next; } while (curNode != m_head); } return false; } /// <summary> /// 지정한 값을 포함하는 가장 처음 일치하는 노드를 반환한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>지정한 값을 포함하는 노드(없는 경우: nullptr)</returns> SinglyCircularLinkedListNode* SinglyCircularLinkedList::Find(int value) { if (m_head != nullptr) { SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { return curNode; } curNode = curNode->m_next; } while (curNode != m_head); } return nullptr; } /// <summary> /// 지정한 값을 포함하는 가장 마지막 일치하는 노드를 반환한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>지정한 값을 포함하는 노드(없는 경우: nullptr)</returns> SinglyCircularLinkedListNode* SinglyCircularLinkedList::FindLast(int value) { SinglyCircularLinkedListNode* matchNode{ nullptr }; if (m_head != nullptr) { SinglyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { matchNode = curNode; } curNode = curNode->m_next; } while (curNode != m_head); } return matchNode; }
이중 원형 연결 리스트 구현
DoublyLinkedList를 변형하여 이중 원형 연결 리스트를 구현한다.
DoublyCircularLinkedList.h
#pragma once #include <xutility> #include <iostream> struct DoublyCircularLinkedListNode { DoublyCircularLinkedListNode() {} DoublyCircularLinkedListNode(int value) { m_data = value; } int m_data{ 0 }; DoublyCircularLinkedListNode* m_prev{ nullptr }; DoublyCircularLinkedListNode* m_next{ nullptr }; }; class DoublyCircularLinkedList { public: #pragma region 생성자 DoublyCircularLinkedList(); DoublyCircularLinkedList(const DoublyCircularLinkedList& other); ~DoublyCircularLinkedList(); #pragma endregion #pragma region 속성 const size_t Count() { return m_count; } DoublyCircularLinkedListNode& First() { return *m_head; } DoublyCircularLinkedListNode& Last() { return *m_tail; } #pragma endregion #pragma region 메서드 void AddFirst(int value); void AddFirst(DoublyCircularLinkedListNode* node); void AddLast(int value); void AddLast(DoublyCircularLinkedListNode* node); void Insert(size_t index, int value); void Insert(size_t index, DoublyCircularLinkedListNode* node); bool RemoveFirst(int value); bool RemoveLast(int value); void Remove(const DoublyCircularLinkedListNode* node); void Clear(); bool Contains(int value); bool Contains(const DoublyCircularLinkedListNode* node); DoublyCircularLinkedListNode* Find(int value); DoublyCircularLinkedListNode* FindLast(int value); #pragma endregion private: #pragma region Class Util DoublyCircularLinkedListNode* PopNode(int value); void PushNode(DoublyCircularLinkedListNode* node); #pragma endregion private: #pragma region 변수 size_t m_count; DoublyCircularLinkedListNode* m_head; DoublyCircularLinkedListNode* m_tail; DoublyCircularLinkedListNode* m_free; #pragma endregion };
생성자, 소멸자 구현
기본 생성자와 DoublyCircularLinkedList를 인자로 받는 복사 생성자를 구현한다.
DoublyCircularLinkedList.cpp
/// <summary> /// 비어있는 DoublyCircularLinkedList를 생성한다. /// </summary> DoublyCircularLinkedList::DoublyCircularLinkedList() : m_count(0), m_head(nullptr), m_tail(nullptr), m_free(nullptr) { } /// <summary> /// 다른 DoublyCircularLinkedList가 가지고 있는 노드를 복사해 DoublyCircularLinkedList를 생성한다. /// </summary> /// <param name="other">기준이 될 DoublyCircularLinkedList</param> DoublyCircularLinkedList::DoublyCircularLinkedList(const DoublyCircularLinkedList& other) : m_count(other.m_count), m_head(nullptr), m_tail(nullptr), m_free(nullptr) { if (other.m_head == nullptr) { return; } DoublyCircularLinkedListNode* curNode{ other.m_head }; m_head = m_tail = PopNode(curNode->m_data); curNode = curNode->m_next; while (curNode->m_prev != other.m_tail) { DoublyCircularLinkedListNode* newNode{ PopNode(curNode->m_data) }; m_tail->m_next = newNode; newNode->m_prev = m_tail; m_tail = newNode; curNode = curNode->m_next; } m_head->m_prev = m_tail; m_tail->m_next = m_head; } /// <summary> /// 메모리 누수를 막기 위해 동적 생성한 노드들을 제거한다. /// </summary> DoublyCircularLinkedList::~DoublyCircularLinkedList() { if (m_head != nullptr) { DoublyCircularLinkedListNode* head{ m_head }; do { DoublyCircularLinkedListNode* curNode{ m_head }; m_head = m_head->m_next; delete curNode; } while (m_head != head); } while (m_free != nullptr) { DoublyCircularLinkedListNode* curNode{ m_free }; m_free = m_free->m_next; delete curNode; } }
데이터 삽입 메서드 구현
데이터 삽입 연산에 필요한 메서드들을 구현한다.
DoublyCircularLinkedList.cpp
/// <summary> /// DoublyCircularLinkedList의 시작 위치에 지정한 값이 포함된 새 노드를 추가한다. /// </summary> /// <param name="value">추가할 값</param> void DoublyCircularLinkedList::AddFirst(int value) { DoublyCircularLinkedListNode* newNode{ PopNode(value) }; AddFirst(newNode); } /// <summary> /// DoublyCircularLinkedList의 시작 위치에 지정한 노드를 추가한다. /// </summary> /// <param name="node">추가할 새 노드</param> void DoublyCircularLinkedList::AddFirst(DoublyCircularLinkedListNode* node) { if (node == nullptr || node->m_prev != nullptr || node->m_next != nullptr) { throw std::invalid_argument("node"); } node->m_next = m_head; if (m_head != nullptr) { m_head->m_prev = node; m_head = node; } else { m_head = m_tail = node; } m_head->m_prev = m_tail; m_tail->m_next = m_head; m_count++; } /// <summary> /// DoublyCircularLinkedList의 끝 위치에 지정한 값이 포함된 새 노드를 추가한다. /// </summary> /// <param name="value">추가할 값</param> void DoublyCircularLinkedList::AddLast(int value) { DoublyCircularLinkedListNode* newNode{ PopNode(value) }; AddLast(newNode); } /// <summary> /// DoublyCircularLinkedList의 끝 위치에 지정한 노드를 추가한다. /// </summary> /// <param name="node">추가할 새 노드</param> void DoublyCircularLinkedList::AddLast(DoublyCircularLinkedListNode* node) { if (node == nullptr || node->m_prev != nullptr || node->m_next != nullptr) { throw std::invalid_argument("node"); } node->m_prev = m_tail; if (m_tail != nullptr) { m_tail->m_next = node; m_tail = node; } else { m_head = m_tail = node; } m_head->m_prev = m_tail; m_tail->m_next = m_head; m_count++; } /// <summary> /// DoublyCircularLinkedList의 지정한 인덱스에 해당하는 위치에 지정한 값이 포함된 새 노드를 추가한다. /// </summary> /// <param name="index">값을 추가할 인덱스</param> /// <param name="value">추가할 값</param> void DoublyCircularLinkedList::Insert(size_t index, int value) { DoublyCircularLinkedListNode* newNode{ PopNode(value) }; Insert(index, newNode); } /// <summary> /// DoublyCircularLinkedList의 지정한 인덱스에 해당하는 위치에 지정된 노드를 추가한다. /// </summary> /// <param name="index">새 노드를 추가할 인덱스</param> /// <param name="node">추가할 새 노드</param> void DoublyCircularLinkedList::Insert(size_t index, DoublyCircularLinkedListNode* node) { if (index > m_count) { throw std::out_of_range("index"); } if (node == nullptr || node->m_next != nullptr) { throw std::invalid_argument("node"); } DoublyCircularLinkedListNode* curNode{ m_head }; DoublyCircularLinkedListNode* prevNode{ m_tail }; for (int i = 0; i < index; i++) { prevNode = curNode; curNode = curNode->m_next; } node->m_prev = prevNode; if (prevNode != nullptr) { prevNode->m_next = node; } node->m_next = curNode; if (curNode != nullptr) { curNode->m_prev = node; } if (index == 0) { m_head = node; } if (index == m_count) { m_tail = node; } m_count++; }
데이터 삭제 메서드 구현
데이터 삭제 연산에 필요한 메서드들을 구현한다.
DoublyCircularLinkedList.cpp
/// <summary> /// DoublyCircularLinkedList에서 가장 처음 일치한 지정된 값을 포함한 노드를 제거한다. /// </summary> /// <param name="value">제거할 값</param> bool DoublyCircularLinkedList::RemoveFirst(int value) { bool foundNode{ false }; DoublyCircularLinkedListNode* curNode{ m_head }; if (m_head != nullptr) { do { if (curNode->m_data == value) { foundNode = true; break; } curNode = curNode->m_next; } while (curNode != m_head); } if (!foundNode) { return false; } Remove(curNode); return true; } /// <summary> /// DoublyCircularLinkedList에서 가장 처음 일치한 지정된 값을 포함한 노드를 제거한다. /// </summary> /// <param name="value">제거할 값</param> bool DoublyCircularLinkedList::RemoveLast(int value) { bool foundNode{ false }; DoublyCircularLinkedListNode* curNode{ m_tail }; if (m_tail != nullptr) { do { if (curNode->m_data == value) { foundNode = true; break; } curNode = curNode->m_prev; } while (curNode != m_tail); } if (!foundNode) { return false; } Remove(curNode); return true; } /// <summary> /// DoublyCircularLinkedList에서 지정된 노드를 제거한다. /// </summary> /// <param name="node">제거할 노드</param> void DoublyCircularLinkedList::Remove(const DoublyCircularLinkedListNode* node) { if (node == nullptr) { throw std::invalid_argument("node"); } bool foundNode{ false }; DoublyCircularLinkedListNode* curNode{ m_head }; if (m_head != nullptr) { do { if (curNode == node) { foundNode = true; break; } curNode = curNode->m_next; } while (curNode != m_head); } if (!foundNode) { throw std::out_of_range("node"); } DoublyCircularLinkedListNode* prevNode{ curNode->m_prev }; DoublyCircularLinkedListNode* nextNode{ curNode->m_next }; prevNode->m_next = nextNode; if (prevNode == m_tail) { m_head = nextNode; } nextNode->m_prev = prevNode; if (nextNode == m_head) { m_tail = prevNode; } if (m_head == m_tail && m_tail == curNode) { m_head = m_tail = nullptr; } PushNode(curNode); m_count--; } /// <summary> /// DoublyCircularLinkedList의 모든 노드를 제거한다. /// </summary> void DoublyCircularLinkedList::Clear() { if (m_head != nullptr) { DoublyCircularLinkedListNode* head{ m_head }; do { DoublyCircularLinkedListNode* curNode{ m_head }; m_head = m_head->m_next; PushNode(curNode); } while (m_head != head); } m_head = m_tail = nullptr; m_count = 0; }
기능 메서드 구현
자료구조를 효율적으로 사용하기 위한 기능 메서드를 구현한다.
DoublyCircularLinkedList.cpp
/// <summary> /// 지정한 값을 포함한 노드가 존재하는지 확인한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>값의 존재 여부</returns> bool DoublyCircularLinkedList::Contains(int value) { if (m_head != nullptr) { DoublyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { return true; } curNode = curNode->m_next; } while (curNode != m_head); } return false; } /// <summary> /// 지정한 노드가 DoublyCircularLinkedList에 포함되는지 확인한다. /// </summary> /// <param name="node">찾을 노드</param> /// <returns>노드의 포함 여부</returns> bool DoublyCircularLinkedList::Contains(const DoublyCircularLinkedListNode* node) { if (m_head != nullptr) { DoublyCircularLinkedListNode* curNode{ m_head }; do { if (curNode == node) { return true; } curNode = curNode->m_next; } while (curNode != m_head); } return false; } /// <summary> /// 지정한 값을 포함하는 가장 처음 일치하는 노드를 반환한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>지정한 값을 포함하는 노드(없는 경우: nullptr)</returns> DoublyCircularLinkedListNode* DoublyCircularLinkedList::Find(int value) { if (m_head != nullptr) { DoublyCircularLinkedListNode* curNode{ m_head }; do { if (curNode->m_data == value) { return curNode; } curNode = curNode->m_next; } while (curNode != m_head); } return nullptr; } /// <summary> /// 지정한 값을 포함하는 가장 마지막 일치하는 노드를 반환한다. /// </summary> /// <param name="value">찾을 값</param> /// <returns>지정한 값을 포함하는 노드(없는 경우: nullptr)</returns> DoublyCircularLinkedListNode* DoublyCircularLinkedList::FindLast(int value) { if (m_tail != nullptr) { DoublyCircularLinkedListNode* curNode{ m_tail }; do { if (curNode->m_data == value) { return curNode; } curNode = curNode->m_prev; } while (curNode != m_tail); } return nullptr; }
- 생성자