공통점

  • 원소의 중복 허용 x
    • 원소의 중복을 원한다면 map라이브러리내 multimap을 활용
  • key : value 구

map

  • 내부 원소가 저장되는 자료구조가 '균형 이진 검색 트리(Balanced binary search tree)'로 구현됨
    • 내부 원소가 저장될 때 알아서 정렬되어 저장 (default는 오름차순)
      • key값이 정렬되므로 key의 순서가 중요한 경우 적합
  • 삽입, 삭제, 검색의 시간 복잡도 O(log n) 보장
  • 트리 구조를 유지하기 위해 노드포인터 등 추가적인 메모리 사용
    • 해시테이블을 유지해야하는 unordered_map에 비해 비교적 메모리 사용이 효율적
  • 정렬 기준 커스터마이징 가능
    • map<{key 타입}, {value 타입}, greater<{key 타입}>> : 내림차순
#include <map>
#include <iostream>
#include <string>
using namespace std;

map<int, string> m;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		string str = "원소";
		str += char(i + '0');
		m[i] = str;
	}

	// 검색
	map<int, string>::iterator itr1 = m.find(4);
	if (itr1 != m.end()) {
		// 찾음
		cout << itr1->first << "\n"; // 4
		cout << itr1->second << "\n"; // 원소4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	m[-1] = "원소-1";
	for (map<int, string>::iterator itr = m.begin(); itr != m.end(); ++itr) {
		cout << itr->second << " "; // -1 0 1 2 3 4 5 6 7 8 9
	}

	// 삭제
	m.erase(5);

	map<int,string>::iterator itr2 = m.find(5);
	if (itr2 != m.end()) {
		// 찾음
		cout << itr2->second << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

unordered_map

  • 내부 원소가 저장되는 자료구조가 '해시 테이블(Hash table)'로 구현됨
    • 내부 원소가 저장될 때 순서가 보장되지 않음
  • 삽입, 삭제, 검색의 평균 시간 복잡도는 O(1)이지만,
    최악의 경우 해시 충돌 시 O(n)
  • 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수에 메모리를 추가 사용
    • 기본적으로 더 많은 메모리를 사용할 가능성이 높다.
  • 사용자 정의 해시 함수와 동일성조건(equal function)을 지정 가능
    • #include <iostream>
      #include <unordered_map>
      
      // 사용자 정의 타입
      struct Point {
          int x, y;
      
          // 동일성 비교를 위한 == 연산자 정의
          bool operator==(const Point& other) const {
              return x == other.x && y == other.y;
          }
      };
      
      // 사용자 정의 해시 함수
      struct PointHash {
          size_t operator()(const Point& point) const {
              return std::hash<int>()(point.x) ^ std::hash<int>()(point.y << 1); // XOR와 시프트 연산
          }
      };
      
      // 사용자 정의 동일성 비교 함수
      struct PointEqual {
          bool operator()(const Point& a, const Point& b) const {
              return a.x == b.x && a.y == b.y;
          }
      };
      
      int main() {
          // 사용자 정의 타입과 해시/비교 함수 지정
          std::unordered_map<Point, std::string, PointHash, PointEqual> pointsMap;
      
          // 데이터 삽입
          pointsMap[{1, 2}] = "Point A";
          pointsMap[{3, 4}] = "Point B";
      
          // 데이터 출력
          for (const auto& pair : pointsMap) {
              std::cout << "(" << pair.first.x << ", " << pair.first.y << "): " << pair.second << '\n';
          }
      
          return 0;
      }
#include <unordered_map>
#include <iostream>
#include <string>
using namespace std;

unordered_map<int, string> um;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		string str = "원소";
		str += char(i + '0');
		um[i] = str;
	}

	// 검색
	unordered_map<int, string>::iterator itr1 = um.find(4);
	if (itr1 != um.end()) {
		// 찾음
		cout << itr1->first << "\n"; // 4
		cout << itr1->second << "\n"; // 원소4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	um[-1] = "원소-1";
	for (unordered_map<int, string>::iterator itr = um.begin(); itr != um.end(); ++itr) {
		cout << itr->second << " "; // 0 1 2 3 4 5 6 7 8 9 -1
	}

	// 삭제
	um.erase(5);

	unordered_map<int,string>::iterator itr2 = um.find(5);
	if (itr2 != um.end()) {
		// 찾음
		cout << itr2->second << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

  map unordered_map
자료구조 균형 이진 검색 트리 해시 테이블
삽입, 검색, 삭제의 시간 복잡도 O(log n) O(1) (최악 O(n))
메모리 사용 트리 구조를 유지하기 위해 노드포인터 등 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수

 

반응형

공통점

  • 원소의 중복 허용 x

set

  • 내부 원소가 저장되는 자료구조가 '균형 이진 검색 트리(Balanced binary search tree)'로 구현됨
    • 내부 원소가 저장될 때 알아서 정렬되어 저장 (default는 오름차순)
  • 삽입, 삭제, 검색의 시간 복잡도 O(log n) 보장
  • 트리 구조를 유지하기 위해 노드포인터 등 추가적인 메모리 사용
  • 정렬 기준 커스터마이징 가능
    • set<int, greater<int>> : 내림차순
#include <set>
#include <iostream>
using namespace std;

set<int> s;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		s.insert(i);
	}

	// 검색
	set<int>::iterator itr1 = s.find(4);
	if (itr1 != s.end()) {
		// 찾음
		cout << *itr1 << "\n"; // 4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
    	s.insert(-1);
	for (set<int>::iterator itr = s.begin(); itr != s.end(); ++itr) {
		cout << *itr << " "; // -1 0 1 2 3 4 5 6 7 8 9
	}

	// 삭제
	s.erase(5);

	set<int>::iterator itr2 = s.find(5);
	if (itr2 != s.end()) {
		// 찾음
		cout << *itr2 << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

unordered_set

  • 내부 원소가 저장되는 자료구조가 '해시 테이블(Hash table)'로 구현됨
    • 내부 원소가 저장될 때 순서가 보장되지 않음
  • 삽입, 삭제, 검색의 평균 시간 복잡도는 O(1)이지만,
    최악의 경우 해시 충돌 시 O(n)
  • 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수에 메모리를 추가 사용
    • 기본적으로 더 많은 메모리를 사용할 가능성이 높다.
  • 사용자 정의 해시 함수와 동일성조건(equal function)을 지정 가능
    • #include <iostream>
      #include <unordered_set>
      #include <string>
      
      // 사용자 정의 해시 함수
      struct CustomHash {
          std::size_t operator()(const std::string& key) const {
              std::size_t hash = 0;
              for (char ch : key) {
                  hash += ch; // 간단한 예: 문자열의 각 문자 값을 더함
              }
              return hash;
          }
      };
      
      // 사용자 정의 동등 비교 함수
      struct CustomEqual {
          bool operator()(const std::string& a, const std::string& b) const {
              return a == b; // 단순히 문자열이 같은지 확인
          }
      };
      
      int main() {
          std::unordered_set<std::string, CustomHash, CustomEqual> customSet;
          customSet.insert("hello");
          customSet.insert("world");
      
          for (const auto& item : customSet) {
              std::cout << item << std::endl;
          }
      
          return 0;
      }
#include <unordered_set>
#include <iostream>
using namespace std;

unordered_set<int> us;

void solution() {
	// 삽입
	for (int i = 0; i < 10; ++i) {
		us.insert(i);
	}

	// 검색
	unordered_set<int>::iterator itr1 = us.find(4);
	if (itr1 != us.end()) {
		// 찾음
		cout << *itr1 << "\n"; // 4
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n";
	}

	// 순회
	us.insert(-1);
	for (unordered_set<int>::iterator itr = us.begin(); itr != us.end(); ++itr) {
		cout << *itr << " "; // 0 1 2 3 4 5 6 7 8 9 -1
	}

	// 삭제
	us.erase(5);

	unordered_set<int>::iterator itr2 = us.find(5);
	if (itr2 != us.end()) {
		// 찾음
		cout << *itr2 << "\n";
	}
	else {
		// 못 찾음
		cout << "nothing" << "\n"; // nothing
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	solution();
	return 0;
}

  set unordered_set
자료구조 균형 이진 검색 트리 해시 테이블
삽입, 검색, 삭제의 시간 복잡도 O(log n) O(1) (최악 O(n))
메모리 사용 트리 구조를 유지하기 위해 노드포인터 등 해시 테이블 구조를 유지하기 위해 버킷과 해시 함수

 

반응형

Resource Acquisition Is Initialization

: C++의 중요한 프로그래밍 원칙으로,
리소스의 할당과 해제를 객체의 생성자와 소멸자를 통해 관리하는 방식
명시적인 리소스 해제 코드없이 자동 소멸되어 관리 됨.

  • 리소스의 할당 -> 객체의 생성자
  • 리소스의 해제 -> 객체의 소멸자

RAII 주요 개념

리소스(예: 메모리, 파일 핸들, 소켓 등)
  1. 리소스 획득은 객체의 생성에 묶인다.
    • 리소스를 객체의 생성자에서 획득
    • 리소스를 사용할 수 있는 상태를 객체의 생명주기에 따라 결정
  2. 리소스 해제는 객체의 소멸에 묶인다.
    • 객체의 소멸자가 호출될 때, 생성자에서 할당된 리소스를 자동으로 해제
    • 명시적으로 리소스를 해제할 필요가 줄어든다.

장점

  • 예외 안전성
    : 예외가 발생하면 소멸자가 호출되어 리소스가 확실히 해제
  • 메모리 누수 방지
    : 소멸자에서 리소스를 해제하므로 메모리 누수를 방지
  • 코드 간결화
    : 리소스 관리를 명시적으로 하지 않아도 됨
  • 가독성 향상
    : 리소스 관리 코드가 간결하고 명확해짐

활용 사례

1. 스마트 포인터

스마트 포인터(std::unique_ptr, std::shared_ptr)는 RAII의 대표적인 구현입니다.
#include <memory>
#include <iostream>

void example() {
    std::unique_ptr<int> ptr(new int(42)); // 리소스 할당
    std::cout << *ptr << std::endl;        // 리소스 사용
} // ptr이 스코프를 벗어나면서 자동으로 delete 호출

2. 파일 핸들 관리

#include <fstream>
#include <iostream>

void example() {
    std::ifstream file("example.txt"); // 파일 열기 (리소스 할당)
    if (!file.is_open()) {
        std::cerr << "파일을 열 수 없습니다.\n";
        return;
    }
    // 파일 작업
} // file이 스코프를 벗어나면서 자동으로 close 호출

3. 뮤텍스 잠금

C++ 표준 라이브러리의 "std::lock_guard"는 RAII로 뮤텍스 잠금을 관리합니다.
#include <mutex>
#include <iostream>

std::mutex mtx;

void example() {
    std::lock_guard<std::mutex> lock(mtx); // 뮤텍스 잠금
    std::cout << "Critical section\n";     // 보호된 코드
} // lock이 스코프를 벗어나면서 자동으로 unlock 호출

 

출처)

 

개체 수명 및 리소스 관리(RAII)

리소스 누수 방지를 위해 최신 C++에서 RAII 원칙을 따릅니다.

learn.microsoft.com

 

반응형

[들어가기 전]

#define R_MAX 20 + 1

~~~

int wall[R_MAX * 2][R_MAX * 2]

위 코드에서 R_MAX * 2는 어떤 정수일까요?

1. 42

2. 22

 

정답을 아시는 분은 뒤로 가셔도 좋습니다! ㅎㅎ

 

정답은 2번 입니다!

왜 이런 결과가 나왔는지는 아래쪽에 있습니다.

 

개인적으로 Problem Solving을 하면서

상수를 저장할 때, 어떤 상수인지 목적을 나타내는 용도나

반복문을 사용하면서 for(int i=s; i<e; ++i)를 매번 작성하기 번거로워 FOR(i,s,e)의 형태로 변환해

사용하곤 했습니다.

개념을 모르고 사용하다보니 Problem Solving중 디버깅을 하면서

이상한 삽질을 해 원인이 무엇인지 알아보게 되어

이 글을 작성하게 되었습니다 😂😂

 

[ #define은 무엇인가? ]

MS의 공식문서에 따르면 #define은 지시문이라 표현하고 있고,

'''토큰 문자열과 식별자 또는 매개 변수가 있는 식별자의 연결인 매크로를 만듭니다. 매크로가 정의된 후 컴파일러는 소스 파일에서 발생하는 각 식별자를 토큰 문자열로 대체할 수 있습니다.'''

라고 합니다.

출처) https://learn.microsoft.com/ko-kr/cpp/preprocessor/hash-define-directive-c-cpp?view=msvc-170

 

#define 지시문(C/C++)

자세한 정보: #define 지시문(C/C++)

learn.microsoft.com

 

하지만 공식문서로 곧바로 이해하기 어려워

타 블로그의 글들을 참고해

이해해보고자했습니다.

 

한 문장으로 표현해보면

매크로를 정의할 때 앞에 써주는 키워드 입니다.

 

# : 전처리 지시자

define : 정의

#define : 컴파일 이전에 정의

 

[사용 방법]

#define "매크로 상수명" "매크로 확장 문자열"
#define "매크로함수명(전달할 인자, ...) "매크로 확장 문자열"

#define DUMMY 213456789
#define FOR(i,s,e) for(int i=s; i<e; ++i)

위와 같이 사용할 수 있습니다.

참고) https://blankspace-dev.tistory.com/353

 

[주의 사항]

이 글을 쓰게 된 원인이 바로 아래와 같은 주의 사항 때문이었습니다.

바로

연산자 우선순위를 명확히 표시해야한다.

입니다.

 

실사례였던 예시로 보여드리겠습니다.

#define R_MAX 20 + 1

~~~

int wall[R_MAX * 2][R_MAX * 2]

저의 목적은 wall배열의 크기를 42 * 42크기로 초기화하고자 하였습니다.

그러나 이 경우 아쉽게도 배열의 크기는 22 * 22입니다.

 

그 이유는 #define은 단순 매크로이지 변수가 아니기 때문입니다.

따라서,

R_MAX * 2 = (20 + 1) * 2 = 21 * 2

42가 아닌,

R_MAX * 2 = 20 + 1 * 2

이므로

22의 값이 나오게 됩니다.

 

그렇기에 42로 나오게 하기 위해선

#define R_MAX (20 + 1)

의 형태로 작성해주어야 합니다.

 

여러분은 저와 같은 삽질 하지 않기를....😂😂😂

(코드내 다른 곳에서 이틀 동안 고민했다는 건 비밀입니다. ㅎㅎ)

 

추가로,

1. 컴파일 후 기호 테이블에 들어가지 않기 때문에 디버깅의 어려움

2. 타입 안전성이 떨어짐

등의 이유가 있다고 합니다.

참고) https://d-yong.tistory.com/13

 

덧붙여 상수의 경우에는 const를 사용하는편이 더 좋다고 합니다.

이 이유도 추후에 찾아봐야겠습니다.

반응형

최근 알고리즘 학습을 하다가 궁금한 점이 생겼다.

(자료형)* (변수명) vs (자료형) *(변수명)

와 같은 *의 위치에 따라 차이가 있는 것일까?

 

정답은 없다.

단지, 개발자의 코딩 스타일의 차이일뿐이라고 한다.

 

두 방법 모두

포인터 변수는 메모리 주소를 저장하고

이 주소에 있는 데이터에 접근을 할 수 있다.

 

컴파일러는 두 가지 모두를 정확하게 이해하고 처리한다.

따라서, 어떤 표기법을 사용하더라도 포인터 변수의 기능은 동일하게 동작한다.

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

틀린 부분이 있다면 조언 주시면 감사하겠습니다.

반응형

+ Recent posts