map

-> map의 원소는 key와 value를 한 쌍으로 가진다.

-> 각 원소들은 삽입 시에 key값에 따라 자동 정렬이 발생한다.(순회 가능)

-> 노드 기반 컨테이너들 중 유일하게 key값을 통한 임의 접근이 가능하다.

-> 리소스 탐색용으로 많이 사용된다.

-> 단, 중복된 key값은 허용하지 않는다.

 

map

->map은 key와 value를 한 쌍으로 가진 컨테이너이다.

-> key로 사용할 자료형과 value로 사용할 자료형을 <>안에 명시해주면 된다.

-> 명시한 순서대로 key와 value의 타입이 결정된다.

map<int, int> Mymap;

map의 원소 삽입

map은 원소 삽입 시 key 값에 따라 자동 정렬이 발생한다.

-> 자동 정렬이 발생하기 때문에 앞, 뒤 원소 삽입의 의미가 없으므로 push 함수를 지원하지 않는다.

 

map의 원소

pair 객체(구조체)

map은 원소로 객체를 가진다.

pair<int, int> Mypair(1, 100);

구조체이기 때문에 읽기/쓰기가 가능하다. 

Mypair.first  = 10;
Mypair.second = 100;

               (key)                   (value)
cout << Mypair.first << ", " << Mypair.second << endl;

map의 원소 삽입 방법

#1. pair 객체

pair<int, int> Mypair<1, 100>;
Mymap,insert(Mypair);

auto iter = Mymap.begin();

for(; iter != Mymap.end(); ++iter)
{
 iter->second += 10;
 cout << iter->first << ", " << iter->second << endl; 
}

pair<int, int>(1, 100);
Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(pair<int, int>(2, 200));
Mymap.insert(pair<int, int>(3, 300));

map의 key값(iter->first)은 상수이기때문에 대입이 불가능하다.

 

#2. make_pair()

인자로 2가지 정볼르 받아 첫 번째 인자는 key값으로, 두 번째 인자는 value값으로 만든 pair를 반환한다.

Mymap.insert(make_pair(1, 100));

Mymap.insert(make_pair(2, 200));

Mymap.insert(make_pair(3, 300));

 

#3. value_type

map 컨테이너 내부에 구현된 pair객체라고 생각하면 된다.

map<int, int>::value_type MyValuetype(1, 100);
cout << MyValuetype.first << ", " << MyValuetype.second << endl;
MyValuetype.first  = 10;		// 불가능
MyValuetype.second = 1000;

Mymap.insert(MyValuetype);
Mymap.insert(map<int, int>::value_type(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));

value_type의 key값(first) 또한 상수이기때문에 대입이 불가능하다.

 

#4. index 방식

-> []를 사용한다.

Mymap[1] = 100;

Mymap[2] = 200;

for(auto pair : Mymap)
{
  cout << pair.first << ", " << pair, second << endl;
}

insert 와 []의 차이점

-> insert : 중복된 key값이 있을 경우 무시한다.

-> [] : 중복된 key값이 있을 경우 value를 수정한다.

 

map은 중복되는 key값을 허용하지 않기 때문에 []을 통한 삽입은 자주 사용하지 않는다.

 

#5. emplace()

모든 컨테이너의 삽입 용어를 획일화 하기 위해 추가되었다.

vector : emplace_back()

list     : emplace_back() / emplace_front()

map   : emplace()

Mymap.emplace(1, 100);
Mymap.emplace(2, 200);
Mymap.emplace(3, 300);

단, key의 타입이 다를 경우 오류가 발생한다.

 

#6. 유니폼 초기화

-> {}를 사용한다.

Mymap.insert({1, 100});

-> 초기화가 필요한 것들에 대해서 모든 초기화가 가능하다.

-> 가독성이 떨어진다는 단점이 있다.

 

map의 삽입 정리

map<int, int>		Mymap;

    // index 방식
	Mymap[0] = 0;
    
    // pair 객체
	Mymap.insert(pair<int, int>(1, 100));
    
    // 첫 번째 인자는 key값으로, 두 번째 인자는 value값으로 만든 pair를 반환
	Mymap.insert(make_pair(2, 200));
    
    // map 컨테이너 내부에 구현된 pair 객체
	Mymap.insert(map<int, int>::value_type(3, 300)); 
    
    // 획일화된 삽입 용어(C++11 문법)
	Mymap.emplace(4, 400);
    
    // 유니폼 초기화(C++11 문법)
	Mymap.insert({ 5, 500 });
}

 

map의 반복자

map<int, int> Mymap;

map<int, int>::iterator iter = Mymap.begin();

++iter;
++iter;

cout << iter->first << ", " << iter->second << endl;

map은 노드기반 컨테이너이기 때문에 양방향접근 반복자를 사용한다.

 

 

map의 중간 삽입

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

map<int, int>::iterator iter = Mymap.begin();
++iter;
++iter;

Mymap.insert(iter, {10, 1000});

for(auto pair : Mymap)
  cout << pair.first << ", " << pair.second << endl;

여기서 우리가 원하는 값은 1, 2, 10, 3, 4, 5 이다.

하지만 map은 key값에 따라 자동 정렬이 발생한다.

그렇기 때문에 결과는 1, 2, 3, 4, 5, 10이 나오게 된다.

-> 중간 삽입의 의미가 없게된다.

 

map의 중간 삭제

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

map<int, int>::iterator		iter = Mymap.begin();
map<int, int>::iterator		iter_end = Mymap.end();
++iter;
++iter;

Mymap.erase(iter);
iter = Mymap.begin();

for(; iter != iter_end; ++iter)
{
   cout << iter->first << ", " << iter->second << endl;
}

map의 중간 삭제는 자동 정렬 발생과는 상관이 없으므로

정상적으로 원하는 값이 나오게 된다.(다른 컨테이너 방식과 같음)

 

map의 탐색_find()

cout << Mymap[10] << endl;

탐색을 하기 위해 []를 사용할 경우 key값이 있으면 value를 반환한다.

 

하지만, key값이 없을 경우 원소를 삽입하게된다.

-> 새로운 원소를 삽입 후에 그 원소에 있는 value 값을 반환

 

find()의 사용 방법은 다음과 같다.

map<int, int>		Mymap;

Mymap.insert(pair<int, int>(1, 100));
Mymap.insert(make_pair(2, 200));
Mymap.insert(map<int, int>::value_type(3, 300));
Mymap.emplace(4, 400);
Mymap.insert({ 5, 500 });

auto iter = Mymap.find(4);

if(iter != Mymap.end())
{
   cout << iter->first << ", " << iter->second << endl;
}

find 함수의 문제점

-> find함수는 단순 비교이다.

map<const char*, int> Mymap;

Mymap.emplace("AAA", 10);
Mymap.emplace("BBB", 20);
Mymap.emplace("CCC", 30);

auto iter = Mymap.find("BBB");

char szBuff[16] = "AAA";
auto iter = Mymap.find(szBuff);

if(iter != Mymap.end())
{
   cout << iter->first << ", " << iter->second << endl;
}
else
{
   cout << "탐색 불가" << endl;
}

 

-> key값으로 저장하는 것은 문자열, Data영역의 주소이다.

-> 탐색하고자 하는 szBuff는 지역 변수 stack영역에 할당이 되므로 탐색 불가가 출력이 된다.

-> 즉, 문자열을 비교하는 것이 아니라 단순 주소를 비교하고 있는 것이다.

 

map 컨테이너의 key값이 포인터일 경우 탐색 방법

-> 알고리즘 함수의 find_if를 사용한다.

-> find_if는 단항 조건자여야 한다.

-> 즉, 함수 객체를 통해서 탐색을 하면 된다.

class CStringCmp
{
public:
   CStringCmp(const char* _pString) : m_pString(_pString) {}
   
public:
   template <typename T>
   bool operator()(T& _dst)
   {
     return !strcmp(m_pString, _Dst.first);
   }
   
private:
   const char* m_pString;
};

void main()
{
  map<const char*, int>		Mymap;
  Mymap.emplace("AAA", 10);
  Mymap.emplace("BBB", 20);
  Mymap.emplace("CCC", 30);

  char	szBuff[16] = "AAA";

	
  auto iter = find_if(Mymap.begin(), Mymap.end(), CStringCmp(szBuff));

  if (iter != Mymap.end())
  {
   	 cout << iter->first << ", " << iter->second << endl;
  }
  else
  {
     cout << "탐색 불가" << endl;
  }
}

'Programming > STL' 카테고리의 다른 글

C++(STL Container) 반복자(iterator)  (0) 2020.09.28
C++(STL Container) vector  (0) 2020.09.27
C++(STL Container) list  (0) 2020.09.27
C++ STL이란?  (0) 2020.09.27
STL map / unordered_map 과 map이 사용하는 자료구조  (0) 2020.05.07

list

-> 노드를 기반으로 하는 컨테이너 이다.

-> 더블 링크드 리스트로 구현되어 있다.

-> 앞, 뒤 노드의 삽입 및 삭제가 가능하다.

각 노드는 연속적인 메모리를 사용하는 것이 아니고,

비 연속적인 메모리에 여기저기 저장되지만 포인터로 각 노드를 연결하여

마치 연속된 메모리를 사용하는 것처럼 보인다.

-> 임의 접근이 불가능

-> 탐색하고자하는 원소가 나올 때까지 모든 노드를 순회해야함

-> 선형 시간 O(n)

삽입 삭제 시에는 단순 포인터의 해제 및 연결만 하기 때문에

메모리를 밀고 당길 필요가 없어 빠르다.(상수 시간 O(1))

메모리의 재할당 및 복사가 필요 없기 때문에 런타임 시에도 삽입 삭제가 용이하다.

 

list의 선언

list<int> listInt;

list의 원소 삽입

listInt.push_back(3);
listInt.push_back(4);
listInt.push_back(5);
listInt.push_front(1);
listInt.push_front(2);

list는 앞에서도 원소를 삽입할 수 있기에 push_front 함수를 지원한다.

 

여기서 원소를 출력해야하는데 list는 배열 기반 컨테이너가 아니므로 인덱스접근이 불가능하다.

list의 모든 원소를 순회하기 위해서는 반복자를 활용해야한다.

list<int>::iterator iter = listInt.begin();

for(; iter != listInt.end(); ++iter)
{
   cout << *iter << endl;
}

그런데 반복자가 아닌 list의 원소를 확인할 수 있는 방법이 있다.

그것은 '범위기반 for문' 과 'auto'이다.

 

auto

-> 자료형이다.

-> 대입 받는 데이터의 타입으로 자동 설정된다.

-> 단, 선언과 동시에 초기화를 진행해야만 한다.

auto a = 10    // int형
auto b = 3.14  // double형
auto c = 3.14f // float형


list<int>::iterator	iter = listInt.begin();
auto		        	iter = listInt.begin();

 

범위기반 for문

-> 배열 또는 컨테이너의 모든 원소를 순회할 때 사용한다.

-> 단, 범위 기반 for문을 사용 중 원소의 개수가 바뀌게 되면 오류가 발생한다.

-> 그러므로 단순 순회 용도로만 사용한다.

 

 

list의 원소 삭제

listInt.pop_back();
listInt.pop_front();

for(auto iNum : listInt)
{
  cout << iNum << endl;
}

 

list의 멤버함수

size(), empty(), clear(), front(), back(), begin(), end(), swap() 등

-> 메모리와 관련된 함수를 제외하고는 vector와 동일하다.

 

list의 반복자

-> 임의 접근이 불가능하기 때문에 임의 접근 반복자를 사용하지 않고,

-> 양 방향 접근 반복자를 사용한다.

list<int> listInt;

auto iter = listInt.begin();

++iter;

 

list의 중간 삽입

auto iter_begin = listInt.begin();
auto iter_end   = listInt.end();

++iter_begin;
++iter_begin;

listInt.insert(iter_begin, 999);

list는 vector와 달리 메모리를 밀고 당길 필요가 없다.

단순 빈 공간에 원소를 삽입 후 포인터 연결만 해제 및 재연결하면 되기 때문에

반복자(begin 과 end)의 위치가 변경되지 않아 무효화가 발생하지 않는다.

 

list의 중간 삭제

auto iter_begin = listInt.begin();
auto iter_end   = listInt.end();

++iter_begin;
++iter_begin;

iter_begin = listInt.erase(iter_begin);

 

list의 정렬

-> list는 멤버 함수로 sort를 지원한다.

template <typename T>
bool Less(T _Left, T _Right)
{
	return _Left < _Right;
}

void main()
{
   list<int> listInt;
   
   listInt.push_back(5);
   listInt.push_back(4);
   listInt.push_back(1);
   listInt.push_back(3);
   listInt.push_back(2);

   listInt.sort();
   listInt.sort(Less<int>);
   listInt.sort(less<int>());
   
   for(auto iNum : listInt)
   {
     cout << iNum << endl;
   }
}

list의 멤버 함수 sort는 아무런 조건도 명시하지 않을 경우 기본 오름차순 정렬이 발생한다.

하지만 functional 또는 템플릿 함수를 만들어서 사용해도 무방하다.

 

list의 멤버 함수안에 있는 sort와 reverse를 함께 사용하면 기본 오름차순에서 내림차순 정렬로 바꿀 수 있다.

listInt.sort();
listInt.reverse();

 

'Programming > STL' 카테고리의 다른 글

C++(STL Container) vector  (0) 2020.09.27
C++(STL Container) map  (0) 2020.09.27
C++ STL이란?  (0) 2020.09.27
STL map / unordered_map 과 map이 사용하는 자료구조  (0) 2020.05.07
STL vector에서 push_back과 emplace_back의 차이점  (0) 2019.12.04

STL

-> Standard Template Library의 약자

-> 표준에 등록된 클래스 템플릿의 집합

-> 자료구조 및 알고리즘 등 사용법이 다른 것을 획일화 시킨 것.

 

STL의 구성 요소

#1. 컨테이너

-> 메모리에 등록된 STL이다.

-> 여러 컨테이너 중 대표적인 것 3가지

 

컨테이너 구분 기준

메모리 등록 방법

#1. 표준 시퀀스 컨테이너(선형적)

-> vector, list

 

#2. 표준 연관 컨테이너(비 선형적)

-> 일렬로 줄 지을 수 없는 방법

-> 대표적으로 트리 구조가 있음.

-> map

 

원소 저장 방법

#1. 배열 기반

-> 배열에 원소를 저장

-> vector

 

#2. 노드 기반

-> 메모리 여기저기 저장하면서 포인터로 연결해 놓는 구조

-> list, map

-> C#에서는 list가 배열 기반 형태로 되어있고, C++에서는 노드 기반으로 되어있다.

 

#2. 알고리즘

-> 대부분의 알고리즘은 전역으로 구성되어 있다.

-> 정렬, 탐색 등 획일화 하기 위해 각 컨테이너의 멤버가 아닌 전역으로 정의되어있다.

 

#3. 반복자

-> 컨테이너를 순회하는데 동일한 방법으로 순회할 수 있도록 만들어 놓았다.

-> STL의 핵심이라고 할 수 있따.

-> 각 컨테이너의 멤버로 정의되어 있다.

 

#4. 함수 객체

-> 말 그대로 함수를 객체처럼 사용하는 것이다.

 

  • map 은 무엇인가?

    map STL에 속한 컨테이너 중 하나로 key와 value로 이루어져 있으며 이는 pair 객체 형태로 저장된다.

  • unordered_map은 무엇인가?

    unordered_map은 hash_table 기반의 hash container 이다.
    기존의 map과 달리 이진 탐색트리가 아닌 해시 테이블로 구성 되어있다.
    언뜻보면 hash_map과 똑같다고 볼 수 있지만 hash_map은 비표준 컨테이너 인데 반해 unordered_map은
    c++11에서 STL 표준 컨테이너로 추가되었으며, hash_map과 거의 동일한 기능을 제공한다고 한다.
    hash_map과 동일하다고 하지만 MSDN에서는 표준 컨테이너인 unordered map 사용을 권장하고 있다.


  • Hash Table은 무엇인가?

    key아 value로 된 쌍을 저장하는 자료구조이고, c#에서 Dictionary와 같다.

  • map과 unordered_map의 차이점

    map은 자료를 저장할 때 자동으로 정렬을하고, unordered_map은 정렬하지 않는다.
    일반적으로 unordered_map이 map보다 성능이 더 좋지만 데이터의 양이 많으면 많을수록 map보다
    성능이 더 떨어진다고 한다. 이유는 균형트리는 이론상 무조건 균형을 맞추게 되어 있기 때문이다.
    따라서 데이터가 작다면 unordered_map을, 크다면 map을 사용하는 것이 바람직하다.

  • map과 unordered_map의 장단점

    map의 장점 : 컨테이너에 속한 list나 vector보다 탐색 속도가 빠르기 때문에 검색에는 최적의 컨테이너이다.
    map의 단점 : 키를 새로 추가할 때마다 정렬된 위치를 찾아서 추가를 해야 하므로 대용량의 맵을 작성하는 데에는
    시간이 많이 소요됨

    unordered_map의 장점 : hash로 인해 key를 통한 value로의 접근이 빠르고 적은 양의 자료에 유리
    unordered_map의 단점 : 해시 충돌로 인하여 성능이 저하 될 수 있고 버킷으로 인해 메모리 사용양이 증가함


  • map과 unordered_map의 시간 복잡도
  map unordered_map
검색 O(logN) O(1)
삽입 O(logN) O(1)
삭제 O(logN) O(1)

 


  • map 활용 시 주의사항


    자료를 저장할 때 자동으로 정렬된 상태로 저장하기 때문에 삽입 삭제가 빈번하면 안됨.


  • map이 사용하는 자료구조

    map이 사용하는 자료구조는  "레드 블랙트리(Red-Black Tree)"라는 자료구조이다.




 

 

 

  • 레드 블랙트리는 무엇인가?

    자가균형이진탐색 트리로써, 대표적으로 "연관배열" 등을 구현하는데 쓰이는 자료구조이다.
    트리에 n개의 원소가 있을때 O(logn)의 시간복잡도로 삽입, 삭제, 검색을 할 수 있다.


  • 레드 블랙트리는 어느 경우에 사용되는가?

    이진 탐색 트리 중에 값이 한쪽으로 치우치게 되는 경우가 있다.
    이런 경우, 이진 탐색 트리의 검색 효율을 나쁘게 하므로, 균형을 바로 잡기 위해서 해당 자료구조를 사용한다.

 

  • map에서 사용하는 이유는? 

    map은 많은 자료를 정렬하여 저장하고 있고 빠른 검색을 필요로 할 때 자주 사용하게된다.
    그러므로 map을 구현을 할 때 레드 블랙트리를 참고하면 좋을 것 같다.
    복잡한 자료구조 이지만, 사용하는데 있어서 효율적이고, 최악의 경우에도 상당히 우수한 검색 속도를 보여준다.

 

 

 

 

'Programming > STL' 카테고리의 다른 글

C++(STL Container) vector  (0) 2020.09.27
C++(STL Container) map  (0) 2020.09.27
C++(STL Container) list  (0) 2020.09.27
C++ STL이란?  (0) 2020.09.27
STL vector에서 push_back과 emplace_back의 차이점  (0) 2019.12.04

+ Recent posts