정신과 시간의 방

전체 글 305

카테고리 설명
게임 프로그래머가 되기 위한 청춘의 기록
작성일
2025. 6. 11. 16:51
작성자
risehyun
작성일
2025. 6. 11. 16:03
작성자
risehyun
작성일
2025. 4. 26. 18:00
작성자
risehyun
작성일
2025. 4. 24. 22:54
작성자
risehyun
작성일
2025. 4. 2. 21:50
작성자
risehyun
작성일
2025. 3. 13. 13:53
작성자
risehyun
서적 '이펙티브 C++'을 공부하면서 개인적인 복습을 목적으로 기록하는 포스팅입니다.
책 전문은 포함되어 있지 않으며, 책을 학습하다가 궁금했던 내용 위주로 보충 공부한 것을 주로 다룹니다.

 

 

1. C++을 언어들의 연합체로 바라보는 안목은 필수

 

[요약]

 

멀티패러다임 언어로서의 C++ 개념 

- 초창기의 C++은 C언어를 기반으로 객체 지향 기능 몇가지가 결합된 형태의, '클래스를 쓰는 C'였습니다.

- 그러나 이후 C++은 현재까지도 계속해서 새로운 문법이 추가되며 확장 및 발전되고 있습니다.

- 그렇기 때문에 오늘날의 C++은 다중패러다임(=멀티패러다임) 프로그래밍 언어라고 불립니다.

- 즉, C++은 절차적 프로그래밍을 기본으로 객체 지향, 함수식, 일반화 프로그래밍을 포함한 메타프로그래밍 개념까지 지원합니다.

- 따라서 C++은 단일 언어라기 보다는, 상관 관계가 있는 여러 하위 언어들을 제공하는 일종의 연합체라고도 할 수 있습니다.

- 여기서 하위 언어에 해당하는 것들은 총 4가지로 C, 객체 지향 개념의 C++, 템플릿 C++, STL이 있습니다.

 

  • [이 관점이 중요한 이유: C++의 어떤 부분을 사용하는지, 그 상황에 따라 변하는 '최적의 법칙']

    C++를 연합체로 바라봐야 하는 가장 큰 이유는, 각각의 하위 언어마다 효율적인 코드를 작성하는 규칙(rule of thumb)이 달라지기 때문입니다. 예를 들어, 함수의 '매개변수를 전달하는 가장 좋은 방법'이라는 단순한 주제조차도 어떤 하위 언어의 관점에서 보느냐에 따라 답이 달라집니다.

    - C 언어 관점에서
    : struct 같은 사용자 정의 타입이라도 크기가 작다면 값으로 전달(pass-by-value)하는 것이 효율적일 때가 많습니다. 포인터를 쓰는 것보다 단순하고 캐시 친화적일 수 있죠.

    - 객체 지향 C++ 관점에서
    : 상속과 다형성이 적용된 복잡한 클래스 객체는 생성/소멸 비용이 크기 때문에, 상수 참조자로 전달(pass-by-reference-to-const)하는 것이 거의 모든 경우에 일반적인 규칙이 됩니다. 불필요한 객체의 복사를 막기 위함이죠.

    - 템플릿 C++ 관점에서
    : 상황이 또 달라집니다. 다양한 타입이 들어올 수 있는 템플릿 코드에서는, 컴파일러의 최적화 방식과 맞물려 오히려 값으로 전달(pass-by-value)하는 것이 더 효율적인 선택지가 될 수 있습니다. (이는 나중에 배울 '이동 의미론'과도 깊은 관련이 있습니다.)

    - STL 관점에서
    : 이터레이터(iterator)와 함수 객체(function object)를 값으로 전달하는 것이 STL의 일반적인 관례입니다. 포인터처럼 동작하지만 포인터는 아닌, STL만의 독자적인 규칙을 따르는 것이 중요합니다.

 

 

2. #define을 쓰려거든 const, enum, inline을 떠올리자

 

[요약]

- #define(매크로)은 선행 처리자(Preprocessor)에 해당됩니다.

- 그러나 선행 처리자는 컴파일러가 소스 코드를 본격적으로 컴파일하기 전에 단순 치환으로 동작합니다.

- 즉, 컴파일러가 관리하는 심볼 테이블에 등록되지 않아 타입 검사와 최적화, 변수명 충돌 방지 등을 제대로 처리할 수 없습니다.

- 이러한 문제를 해결하기 위해서 컴파일러가 이해할 수 있는 방식을 사용하는 것이 좋습니다.

- 목적에 따른 #define의 대안은 아래와 같습니다.

 

목적 #define 대안 이유
상수 정의 const / constexpr 타입 정보 + 컴파일 타임 검사 가능
열거 값 정의 enum / enum class 네임스페이스 분리 및 정수형 연산 안전
간단한 매크로 함수 inline 함수 타입 안전 + 디버깅 쉬움 + 매개변수 평가 문제 없음
작성일
2025. 1. 5. 23:52
작성자
risehyun
  • 문제

https://school.programmers.co.kr/learn/courses/30/lessons/43162

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

 

  • 풀이
#include <string>
#include <vector>

using namespace std;


void dfs(int currentComputer, const std::vector<std::vector<int>>& computers, std::vector<bool>& visited)
{
	// 우선 현재 컴퓨터에 방문했다는 걸 체크하기 위해 BOOL 값을 TRUE로 변경 
	visited[currentComputer] = true;
	
	// 현재 컴퓨터와 연결된 다른 컴퓨터를 모두 확인함 
	for (int otherComputer = 0; otherComputer < computers.size(); ++otherComputer)
	{
		// 만약 현재 컴퓨터와 다른 컴퓨터가 서로 연결(1)되어 있으면서
                // 해당하는 다른 컴퓨터에 방문한 적이 없다면 
		if (computers[currentComputer][otherComputer] == 1 && !visited[otherComputer])
		{
			// 그 다른 컴퓨터를 새로운 시작점으로 삼고 새로운 탐색을 재귀적으로 시작함. 
			dfs(otherComputer, computers, visited);
		}
	}
}

int solution(int n, vector<vector<int>> computers) 
{
	// 정답을 담을 변수 선언 
    int answer = 0;
    
    // 방문 여부를 확인하기 위한 bool 배열 선언, 초기값은 false로 일괄 초기화 
    std::vector<bool> visited(n, false);
    
    // 네트워크 수를 카운팅하기 위한 변수 선언 
    int networkCount = 0;
    
    // 컴퓨터의 갯수만큼 반복하면서, 확인(=방문)하지 않은 컴퓨터면
    for (int i = 0; i < computers.size(); ++i)
	{
		if(!visited[i])
        {
        	// 네트워크 수를 하나 늘려주고, 이 컴퓨터와 연결된 모든 컴퓨터를 찾기 위해 
            networkCount++;
            // DFS 함수를 호출함 
		    dfs(i, computers, visited);	
	    }
	}

    answer = networkCount;
    
    return answer;
}

 

  • 정리
항목 설명
시간복잡도 O(N^2), 여기서 N은 컴퓨터의 개수(n)

1. dfs 함수가 한 번 호출되면 함수 내의 for문은 computers 배열 크기인 N번 반복하며 특정 컴퓨터와 연결된 모든 컴퓨터를 확인

2. 전체 수행 관점에서, 메인 for문도  n번 반복되지만 dfs 함수는 visited 배열을 사용해 방문 여부를 체크하므로 중복 체크를 하지 않아 각 컴퓨터에 대해 한 번 호출

3. 결론 : 모든 컴퓨터를 한 번씩만, 방문할 때는 자신과 연결될 가능성이 있는 다른 모든 컴퓨터를 n개 확인하므로 전체 연산량은

N(총 컴퓨터수) X N(각 컴퓨터가 확인하는 연결 수) = N^2
공간복잡도 O(N)

1. 방문 기록 배열(visited)를 컴퓨터 개수 N에 비례하는 크기의 배열을 사용해 방문 여부를 기록하므로 O(N)의 공간이 필요함

2. DFS는 재귀적으로 함수를 호출하므로, 최악의 경우 모든 컴퓨터가 한 줄로 연결된 형태라면 재귀 호출의 깊이가 N에 이를 수 있으므로 호출 스택에 필요한 메모리 공간 역시 O(N)
유형 그래프 탐색(인접 행렬 사용)
해결 알고리즘 DFS 또는 BFS 중 어떤 것을 사용해도 해결할 수 있으며
두 방법 모두 시간 및 공간 복잡도는 동일함

 

작성일
2025. 1. 2. 23:58
작성자
risehyun
  • 문제

https://school.programmers.co.kr/learn/courses/30/lessons/1844

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

  • 풀이
// 프로그래머스 - 게임 맵 최단거리 (BFS)

#include <vector>
#include <queue>

using namespace std;

int solution(vector<vector<int>> maps)
{
	// 최종 값 (최단이동거리)를 저장할 변수 
	int answer = 0;
	
	// 맵의 가로 
	int n = maps.size();
	
	// 맵의 세로 
	int m = maps[0].size();
	
	
	// BFS 작동을 위해 사용할 방문 노드, 즉 좌표를 int, int 페어로 queue에 담아줄 예정 
	queue<pair<int, int>> q;
	
	// queue에 초기 시작점인 (0, 0)을 추가해 탐색을 시작 
	q.push({0, 0});
	
	
	// 상, 하, 좌, 우에 해당하는 방향 체크를 위한 방향 벡터 선언 
	int dx = { 0, 0, -1, 1 };
	int dy = { -1, 1, 0, 0 };
	
	// queue가 텅 빌때까지, 즉 더 이상 방문할 노드가 없을 때까지 루프를 실행 
	while(!q.empty())
	{
		// 큐의 가장 앞에 있는 노드(현재 위치)의 좌표를 가져옴. 
		auto[x, y] = q.front();
		
		// 처리한 노드는 다음 위치를 사용하기 위해서 바로 큐에서 제거 
		q.pop(); 
		
		// 현재 위치에서 상하좌우 네 방향을 모두 확인하기 시작함 
		for (int i = 0; i < 4; i++)
		{
			// 현재 x, y 좌표에 체크할 방향의 벡터를 더해 다음으로 이동할 위치의 좌표를 생성 
			int newX = x + dx[i];
			int newY = y + dy[i];
			
			// 이동할 위치가 맵의 유효 범위 내에 있는지 확인하고
			if (newX < 0 || newY < 0 || newX >= n || newY >= m)
			{
                // 맵을 벗어난 경우에는  continue로 무시 
				continue;
			}
			
			// 만약 이동할 위치가 벽(0)이면 방문할 수 없으므로 무시 
			if (maps[newX][newY] != 1)
			{
				continue;
			}
			
			// 유효 범위의 좌표에 무사히 이동했으므로, 이동 거리를 1 늘려줌. 
			map[newX][newY] = maps[x][y] + 1;
			
			// 갱신된 좌표를 다시 queue에 넣어 다음 이동을 준비함. 
			q.push({newX, newY});
		}
	}
	
    // 모든 탐색이 끝난 후, 도착 지점(n - 1, m - 1)의 값을 확인함.
	// 만약 값이 여전히 1인 경우 전혀 이동하지 않았다는 뜻이므로 이동이 불가능한 맵임을 알 수 있음
	// 따라서 이 경우에는 -1을 출력하고, 아닌 경우에만 도출된 최종 이동거리를 답으로 출력함. 
	answer = map[n - 1][m - 1] == 1 ? -1 : map[n - 1][m - 1];
	return answer;
}

 

  • 정리
항목 설명
문제 유형 BFS, 최단 거리 탐색
큐 사용 이유 거리 순으로 탐색하므로 최단 거리 보장
시간 복잡도 O(N×M), 최악의 경우 전체 맵 순회
탐색 방향 4방향 (상하좌우)