게임 개발에서는 실시간 처리, 예측 가능한 물리 연산, AI, 렌더링 등 다양한 측면에서 성능 최적화가 필수입니다. 자료구조와 알고리즘은 데이터 저장, 검색, 정렬, 경로 탐색, 충돌 판정 등 여러 작업의 기반 역할을 하며, 전체 시스템 성능과 안정성에 큰 영향을 준다.
std::vector):reserve)을 통해 재할당 횟수와 메모리 단편화 완화 가능실습 예제:
std::vector의 요소 추가 시 내부 배열의 메모리 주소 변화 확인 코드 제공
#include <iostream>
#include <vector>
int main() {
// 초기 용량을 5로 예약하여, 5개 채워지면 재할당이 발생하도록 합니다.
std::vector<int> vec;
vec.reserve(5);
std::cout << "Initial capacity: " << vec.capacity() << "\n";
// 이전 메모리 주소를 저장할 포인터 변수 (초기에는 nullptr)
int* prevAddress = nullptr;
for (int i = 0; i < 50; i++) {
vec.push_back(i);
int* currAddress = vec.data();
// 재할당이 발생하면 주소가 달라집니다.
if (prevAddress != currAddress) {
if (prevAddress == nullptr) {
// 최초 할당 시 로그
std::cout << "After push_back(" << i << "): Initial allocation.\n"
<< "New Address = " << currAddress
<< ", Capacity = " << vec.capacity() << "\n";
} else {
// 재할당 발생 시 로그: 이전 주소와 새로운 주소를 명시적으로 출력
std::cout << "After push_back(" << i << "): Reallocation occurred!\n"
<< "Old Address = " << prevAddress << " --> New Address = "
<< currAddress << "\n"
<< "New Capacity = " << vec.capacity() << "\n";
}
prevAddress = currAddress;
}
}
return 0;
}
#include <iostream>
// 이중 연결 리스트의 각 노드를 정의하는 구조체
struct Node {
int data; // 노드가 저장할 데이터
Node* prev; // 이전 노드에 대한 포인터
Node* next; // 다음 노드에 대한 포인터
};
// 이중 연결 리스트를 관리하는 클래스
class DoublyLinkedList {
private:
Node* head; // 리스트의 시작 노드 (첫 번째 노드)
Node* tail; // 리스트의 마지막 노드 (마지막 노드)
public:
// 생성자: 리스트가 처음 생성될 때 head와 tail을 nullptr로 초기화
DoublyLinkedList() : head(nullptr), tail(nullptr) {}
// 소멸자: 리스트의 모든 노드를 순회하며 메모리를 해제
~DoublyLinkedList() {
Node* current = head;
// 현재 노드가 nullptr가 될 때까지 순차적으로 삭제
while (current != nullptr) {
Node* nextNode = current->next; // 다음 노드를 임시 저장
delete current; // 현재 노드 메모리 해제
current = nextNode; // 다음 노드로 이동
}
}
// 리스트의 끝에 새 노드를 삽입하는 메서드
void insert(int value) {
// 새 노드를 동적 할당하여 생성, prev와 next는 nullptr로 초기화
Node* newNode = new Node{value, nullptr, nullptr};
// 만약 리스트가 비어있다면, 새 노드가 head와 tail이 됨
if (head == nullptr) {
head = newNode;
tail = newNode;
} else {
// 리스트가 비어있지 않다면, 새 노드를 tail 뒤에 추가
tail->next = newNode; // 기존 tail의 next가 새 노드를 가리킴
newNode->prev = tail; // 새 노드의 prev는 기존 tail을 가리킴
tail = newNode; // tail 포인터를 새 노드로 갱신
}
}
// 특정 값을 가진 노드를 삭제하는 메서드
// 값이 리스트에 존재하면 삭제 후 true를, 없으면 false를 반환
bool remove(int value) {
Node* current = head;
// 삭제할 노드를 찾기 위해 리스트 순회
while (current != nullptr && current->data != value) {
current = current->next;
}
// 만약 해당 값을 가진 노드가 없다면 false 반환
if (current == nullptr) {
return false;
}
// 삭제할 노드가 head인 경우
if (current == head) {
head = current->next; // head를 다음 노드로 갱신
if (head != nullptr) {
head->prev = nullptr; // 새 head의 prev를 nullptr로 설정
} else {
// 리스트에 노드가 하나뿐이었다면 tail도 nullptr로 설정
tail = nullptr;
}
}
// 삭제할 노드가 tail인 경우
else if (current == tail) {
tail = current->prev; // tail을 이전 노드로 갱신
tail->next = nullptr; // 새 tail의 next를 nullptr로 설정
}
// 삭제할 노드가 중간에 위치한 경우
else {
// 현재 노드의 이전 노드와 다음 노드를 서로 연결
current->prev->next = current->next;
current->next->prev = current->prev;
}
delete current; // 삭제할 노드의 메모리 해제
return true;
}
// 리스트를 순방향(앞에서 뒤로)으로 출력하는 메서드
void displayForward() const {
Node* current = head;
std::cout << "Forward: ";
// 리스트의 끝(현재 노드가 nullptr)이 될 때까지 순차적으로 출력
while (current != nullptr) {
std::cout << current->data << " -> ";
current = current->next;
}
std::cout << "NULL" << std::endl;
}
// 리스트를 역방향(뒤에서 앞으로)으로 출력하는 메서드
void displayBackward() const {
Node* current = tail;
std::cout << "Backward: ";
// tail부터 시작하여 이전 노드를 따라가며 출력
while (current != nullptr) {
std::cout << current->data << " -> ";
current = current->prev;
}
std::cout << "NULL" << std::endl;
}
};
int main() {
// DoublyLinkedList 객체 생성
DoublyLinkedList list;
// 리스트에 여러 값을 삽입
list.insert(10);
list.insert(20);
list.insert(30);
list.insert(40);
list.insert(50);
// 순방향 출력: head에서 tail 방향으로 출력
std::cout << "Initial Doubly Linked List:" << std::endl;
list.displayForward();
// 역방향 출력: tail에서 head 방향으로 출력
list.displayBackward();
// 특정 값을 가진 노드 삭제 예제 (값 30 삭제)
std::cout << "\nRemoving node with value 30..." << std::endl;
if (list.remove(30)) {
std::cout << "Node removed successfully." << std::endl;
} else {
std::cout << "Node not found." << std::endl;
}
// 삭제 후 리스트 출력
std::cout << "\nDoubly Linked List after deletion:" << std::endl;
list.displayForward();
list.displayBackward();
return 0;
}
unordered_map을 사용해 키-값 삽입, 검색, 출력 확인#include <iostream>
#include <unordered_map>
#include <string>
int main() {
// unordered_map은 C++ STL에서 제공하는 해시 테이블 구현체입니다.
std::unordered_map<std::string, int> hashTable;
// 키-값 쌍을 해시 테이블에 삽입합니다.
hashTable["apple"] = 3;
hashTable["banana"] = 5;
hashTable["cherry"] = 7;
// "apple" 키를 통한 데이터 접근 (평균 O(1) 시간 복잡도)
std::string key = "apple";
if (hashTable.find(key) != hashTable.end()) {
std::cout << "Key: " << key << ", Value: " << hashTable[key] << std::endl;
} else {
std::cout << key << " not found in the hash table." << std::endl;
}
// 해시 테이블의 모든 요소를 출력합니다.
std::cout << "Hash Table Contents:" << std::endl;
for (const auto& pair : hashTable) {
std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
}
return 0;
}
코드 예제
아래는 C++로 이진 탐색 트리(Binary Search Tree)를 간단하게 구현한 예제입니다.
이 예제에서는 노드 구조체를 정의하고, 데이터를 삽입하며 중위 순회(Inorder Traversal)를 통해 트리 내의 데이터를 정렬된 순서로 출력하는 방법을 보여줍니다.
#include <iostream>
// 이진 탐색 트리의 노드 구조체 정의
struct TreeNode {
int data; // 노드가 저장하는 데이터
TreeNode* left; // 왼쪽 자식 노드를 가리키는 포인터
TreeNode* right; // 오른쪽 자식 노드를 가리키는 포인터
// 생성자: 초기화 리스트를 사용하여 데이터와 포인터들을 초기화합니다.
TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
// 트리에 새로운 데이터를 삽입하는 함수 (재귀적 구현)
TreeNode* insert(TreeNode* root, int value) {
// 만약 트리가 비어있다면 새로운 노드를 생성하여 반환
if (root == nullptr) {
return new TreeNode(value);
}
// 현재 노드의 값보다 작으면 왼쪽 서브트리에 삽입
if (value < root->data) {
root->left = insert(root->left, value);
}
// 현재 노드의 값보다 크면 오른쪽 서브트리에 삽입
else if (value > root->data) {
root->right = insert(root->right, value);
}
// 값이 중복될 경우 특별한 처리를 하지 않고 그대로 반환 (중복 허용하지 않음)
return root;
}
// 중위 순회를 이용해 트리의 데이터를 출력하는 함수
void inorderTraversal(TreeNode* root) {
if (root != nullptr) {
inorderTraversal(root->left);
std::cout << root->data << " ";
inorderTraversal(root->right);
}
}
// 트리의 메모리를 해제하는 함수 (후위 순회 방식)
void deleteTree(TreeNode* root) {
if (root != nullptr) {
deleteTree(root->left);
deleteTree(root->right);
delete root;
}
}
int main() {
TreeNode* root = nullptr;
// 이진 탐색 트리에 데이터 삽입
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);
// 중위 순회를 통해 트리의 데이터를 정렬된 순서로 출력
std::cout << "Inorder Traversal of BST: ";
inorderTraversal(root);
std::cout << std::endl;
// 동적 할당된 트리의 메모리 해제
deleteTree(root);
return 0;
}
이 문서는 강의의 핵심 개념 및 실습 예제를 바탕으로 작성되었으며, 블로그(벨로그) 포스팅용으로 자료구조와 알고리즘의 중요성을 정리하는 데 활용할 수 있다.