std::list (데이터 순차적 저장 및 접근, 데이터 중복 O)

- 중간에 데이터 삽입 및 삭제가 자주 일어날 때 사용한다.
- <list> header를 include 해준 뒤에, std::list를 사용할 수 있다.
- 데이터에 Random Access 하는 경우가 적을 때, 사용하는 것이 좋다. (리스트는 순차적 접근만 가능하므로)
- 저장할 데이터 개수가 많거나, 검색을 자주하는 경우에는 사용하지 않는 것이 좋다.
- 가변적인 길이를 가질 수 있으며, 중간에 비어있는 데이터가 존재하지 않는다.

- list 코드 예시

#include <iostream>
#include <list>

using namespace std;

int main(){
  list<int> lt;
  lt.push_back(2); // lt = {2}
  lt.push_back(3); // lt = {2, 3}
  lt.push_back(4); // lt = {2, 3, 4}
  lt.push_back(4); // lt = {2, 3, 4, 4}
  
  lt.push_front(1); // lt = {1, 2, 3, 4, 4}
  lt.push_front(0); // lt = {0, 1, 2, 3, 4, 4}

  lt.pop_front();   // lt = {1, 2, 3, 4, 4}
  lt.pop_back();    // lt = {1, 2, 3, 4}
  
  lt.push_back(1);  // lt = {1, 2, 3, 4, 1}
  lt.remove(1);     // lt = {2, 3, 4} (특정값 원소 모두 제거)

  // 'list iterator'를 이용하여 list에 접근할 수 있음.
  list<int>::iterator lt_iter;
  for (lt_iter = lt.begin(); lt_iter != lt.end(); lt_iter++) 
      cout << *lt_iter << " ";
  printf("\n");

  // 특정값들로 list 선언하는 법
  list<int> lt2 = {5, 7, 6, 8};

  // 오름차순으로 정렬
  lt2.sort();  // lt2 = {5, 6, 7, 8}

  // lt와 lt2를 merge sort로 합침 (lt2에 lt 원소들을 추가)
  lt2.merge(lt); // lt2 = {2, 3, 4, 5, 6, 7, 8}

  list<int>::iterator lt2_iter;
  for (lt2_iter = lt2.begin(); lt2_iter != lt2.end(); lt2_iter++)
	  cout << *lt2_iter << " ";
  printf("\n");

  return 0;
}

 


std::map (Key 중복 X, Value 중복 O)

- 많은 자료들 사이에서 검색이 용이해야 할 때 사용한다.
- <map> header를 include 해준 뒤에, std::map를 사용할 수 있다.
- Key와 Value는 pair 형태이다.
- C++의 std::map의 경우, key를 기준으로 오름차순 정렬을 자동으로 수행한다.
- 많은 데이터를 저장해야 하고, 검색이 빨라야 할 때 사용하면 좋다.
- 인덱스를 사용하지 않고, iterator를 사용한다.
- 중복 Key value는 사용이 불가능하다. (= 각 Key value는 unique 해야 한다.)

- map 코드 예시

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main(){
  // <key, value>의 pair 형태로 map을 선언함.
  map<string, int> m;

  // map에 데이터 삽입 (key는 중복불가, value는 중복가능)
  m.insert({"ABC", 123});
  m.insert({"BCD", 456});
  m.insert({"CDE", 123});
  // m.insert({"ABC", 789}); 는 key 중복으로 데이터 삽입이 되지 않음.

  // 'map iterator'를 이용하여 map에 접근할 수 있음.
  map<string, int>::iterator m_iter;
  for(m_iter = m.begin(); m_iter != m.end(); m_iter++)
    cout << "key: " <<  m_iter->first << ", value: " << m_iter->second << endl;

  // map에서 데이터 검색 (iterator를 반환하고, 못찾을 시 m.end()를 반환)
  map<string, int>::iterator find_iter;
  find_iter = m.find("ABC");
  
  if(find_iter != m.end())
    cout << "found key: " << find_iter->first << ", found value: " << find_iter->second << endl;
  else
    cout << "can't find data in map!" << endl;

  // map에서 데이터 변경
  m["ABC"] = 789;
  m["BCD"] = 101112;
  
  return 0;
}


std::unordered_map (hash_map) (Key 중복 X, Value 중복 O)

- 많은 자료들 사이에서 검색이 용이해야 할 때 사용한다.
- <unordered_map> header를 include 해준 뒤에, std::unordered_map를 사용할 수 있다.
- Key와 Value는 pair 형태이다.
- c++ std::map과 다르게, hash_map은 자동으로 정렬을 수행하지 않는다. (넣은 순서대로 데이터를 유지)
- 많은 데이터를 저장해야 하고, 검색이 빨라야 할 때 사용하면 좋다.
- 인덱스를 사용하지 않고, iterator를 사용한다.
- 중복 Key value는 사용이 불가능하다. (= 각 Key value는 unique 해야 한다.)

- unordered_map (hash_map) 코드 예시

#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

int main(){
  // <key, value>의 pair 형태로 unordered_map을 선언함.
  unordered_map<string, int> um;

  // unordered_map에 데이터 삽입 (key는 중복불가, value는 중복가능)
  um.insert({"ABC", 123});
  um.insert({"BCD", 456});
  um.insert({"CDE", 789});
  // um.insert({"ABC", 789}); 는 key 중복으로 데이터 삽입이 되지 않음.

  // 'unordered_map iterator'를 이용하여 unordered_map에 접근할 수 있음.
  unordered_map<string, int>::iterator um_iter;
  for (um_iter = um.begin(); um_iter != um.end(); um_iter++)
	  cout << "key: " << um_iter->first << ", value: " << um_iter->second << endl;

  // unordered_map에서 데이터 검색 (iterator를 반환하고, 못찾을 시 m.end()를 반환)
  unordered_map<string, int>::iterator find_iter;
  find_iter = um.find("ABC");

  if (find_iter != um.end())
	  cout << "found key: " << find_iter->first << ", found value: " << find_iter->second << endl;
  else
	  cout << "can't find data in unordered_map!" << endl;

  // unordered_map에서 데이터 삭제
  um.erase(find_iter); // <"ABC", 123> pair 삭제

  // unordered_map에서 데이터 변경
  um["BCD"] = 888;
  um["CDE"] = 999;

  return 0;
}

 


std::set (데이터 중복 X, 순서보장 X)

- 많은 자료들 사이에서 검색이 용이해야 할 때 사용한다.
- 인덱스를 사용하지 않고, iterator를 사용한다.
- 중복 Key value는 사용이 불가능하다. (= 각 Key value는 unique 해야 한다.)
- 이진 균형트리로 자료구조가 구성되어 있다.

- set 코드 예시

#include <iostream>
#include <string>
#include <set>

using namespace std;

int main(){
  // int type의 데이터를 담는 set 선언
  set<int> s;

  // set에 데이터 삽입 (key는 중복불가, value는 중복가능)
  s.insert(1); // s = {1}
  s.insert(2); // s = {1, 2}
  s.insert(3); // s = {1, 2, 3}
  s.insert(4); // s = {1, 2, 3, 4}
  //s.insert(1); 는 key 중복으로 데이터 삽입이 되지 않음.

  // 'set iterator'를 이용하여 set에 접근할 수 있음.
  set<int>::iterator s_iter;
  for (s_iter = s.begin(); s_iter != s.end(); s_iter++)
	  cout << "value: " << *s_iter << endl;

  // set에서 데이터 검색 (iterator를 반환하고, 못찾을 시 s.end()를 반환)
  set<int>::iterator find_iter;
  find_iter = s.find(1);

  if (find_iter != s.end())
	  cout << "found value: " << *find_iter << endl;
  else
	  cout << "can't find data in set!" << endl;

  // set에서 데이터 삭제
  s.erase(find_iter); // '1' value 삭제

  return 0;
}

 


* 참고자료

1. https://gamdekong.tistory.com/94
2. https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=psd0217&logNo=220308769007
3. https://velog.io/@esun1903/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-List-Map-Set%EC%9D%98-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EC%B0%A8%EC%9D%B4%EC%A0%90
4. https://blockdmask.tistory.com/79

std::string class?

- c++에서는 문자열을 다루기 위해 std::string class를 사용할 수 있다.
- <string> header를 include 해준 뒤에, string class를 사용할 수 있다.
- 코딩테스트에서 문자열을 처리하는 문제가 빈번하게 나오니, 미리 사용법을 알아두면 좋을 것 같다.


std::string 사용법

- C++ code에서 string를 사용하기 위해서는 <string> header를 include해주어야 한다.

#include <string>

* 선언 (Declaration)

- 기본적인 string 선언법

#include <string>

using namespace std;

string s("Hello World!");
string s1 = "Hello World!";
string s2(s1);

 

* 사용법

1. string에서의 위치 지칭 및 string의 크기 구하기
  - N 크기의 길이를 가지는 string의 indexing은 0부터 N-1 까지 가능하다.
  - string class는 c언어의 char*(char배열)과는 달리 '\0'가 마지막에 삽입되지 않아도 된다.
  (예를 들어, 5크기의 길이를 가지는 string은 0~4까지의 index에 접근할 수 있다.)

string s = "ABCDE";

int s_len = s.size();    // size of string (5)
int s_len2 = s.length(); // size of string (5)

string::iterator begin_it = s.begin();  // first element를 가르키는 iterator
string::iterator end_it = s.end();      // end element를 가르키는 iterator

char front_val = s.front();  // front_val: 'A'
char back_val = s.back();    // back_val: 'E'

char first_val  = s[0];      // first_val: 'A'
char first_val2 = s.at(0);   // first_val2: 'A'
char second_val  = s[1];     // second_val: 'B'
char second_val2 = s.at(1);  // second_val2: 'B'
char last_val  = s[4];           // last_val: 'E'
char last_val2 = s[s_len-1];     // last_val2: 'E'
char last_val3 = s.at(4);        // last_val3: 'E'
char last_val4 = s.at(s_len-1);  // last_val4: 'E'

 

2. string에서의 값 추가 및 삭제, 복사, 검색
  - find()는 앞에서부터, rfind()는 뒤에서부터 검색 문자(열)을 찾아, 해당 index를 반환해준다.
  (못 찾았을 경우에, 'std::npos' 라는 쓰레기값을 반환해준다.)

- vector에서와 같이, string에서도 push_back()과 pop_back()이 가능하다.
- string은 원하는 곳에 문자(열)을 삽입하거나 삭제할 수 있지만, 시간복잡도가 O(n)이 소요된다.
- string에서 맨 뒤에 문자(열)을 삽입하는 것은 append()나 '+' operation을 사용하면 된다.
- clear() 함수를 사용하면 해당 string 변수를 size가 0인 string으로 초기화 할 수 있다.

string s = "ABCDE";

int B_idx = s.find('B');   // B_index: 1
int D_idx = s.rfind('DE'); // D_index: 3

s.insert(0, "123");   // s: "123ABCDE"
s.append("456");      // s: "123ABCDE456"
s = s + "FGH";        // s: "123ABCDE456FGH"

s.erase(0, 3);        // s: "ABCDE456FGH" (0~2[=3-1] index 값을 삭제)
s.erase(s.find('4')); // s: "ABCDE" (index 0부터 검색하여 최초의 '4' 위치 이후의 값을 다 삭제)

s.clear();            // Initialize string variable 's' 
s.push_back('F');     // s: "F"
s.push_back('G');     // s: "FG"
s.pop_back();         // s: "F"

 

3. string class에서의 유용한 기능들 (비교, substring, char*로의 변환)
  - 문자열을 비교하는 compare()는 같을 때 0, 다를 경우 -1 혹은 1을 반환한다.
    ([숫자 < 영어대문자 < 영어소문자] 순으로 비교를 진행하여 자신이 더 크면 1, 작으면 -1을 반환한다.)

  - 'substr()'의 경우, 한개의 parameter만 입력 시 문자열의 끝부분까지 잘라서 반환한다.
    (만약 2개의 parameter를 입력한다면, 그 길이만큼만 잘라서 반환한다.)

  - 'c_str()'을 이용하여 c++ class인 string에서 c type의 char* 로의 변환이 가능하다.

string s = "ABCDE";

int c_val;
c_val = s.compare("ABCDE"); // c_val: 0
c_val = s.compare("abcde"); // c_val: -1
c_val = s.compare("12345"); // c_val: 1

string sub_str1 = s.substr(2);       // sub_str1: "CDE"
string sub_str2 = s.substr(1, 3);    // sub_str2: "BCD"
string sub_str3 = s.substr(2, 5000); // sub_str3: "CDE"

const char* ctype_c = s.c_str(); // ctype_c: "ABCDE"

 

4. string을 특정 delimiter을 통해 parsing(tokenizing)하기
(1) istringstream class (delimiter가 1개일 때)
  - string을 parsing(tokenizing)하는 방법 중 하나로 istringstream class을 사용할 수 있다.
  - stringstream class는 <sstream>을 include 한 뒤에 사용할 수 있다.

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main()
{
	string line = "first second third fourth";
	stringstream sstream(line);
	string token;

	while (getline(sstream, token, ' '))
	  cout << token << endl;
	/* output:
	first
	second
	third
	fourth
	*/
	return 0;
}

 

(2) find와 substr을 이용하는 방법 (delimiter가 2개 이상일 때)
- string을 parsing(tokenizing)하는 방법 중 하나로 istringstream class을 사용할 수 있다.
- stringstream class는 <sstream>을 include 한 뒤에 사용할 수 있다.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int main()
{
	string s = "apple,pear,peach,melon";
	vector<string> v;

	int prev_idx = 0;
	int cur_idx = s.find(",");

	while (cur_idx != string::npos) {
		string sub_s = s.substr(prev_idx, cur_idx - prev_idx);
		v.push_back(sub_s);
		prev_idx = cur_idx + 1;
		cur_idx = s.find(',', prev_idx);
	}

	v.push_back(s.substr(prev_idx, cur_idx - prev_idx));

	for (int i=0; i<v.size(); i++) 
		cout << v[i] << endl;
	/* output:
    apple
    pear
    peach
    melon
    */
	
	return 0;
}
 
 

(3) 정규 표현식(Regular Expression)을 사용하는 방법
- 정규표현식을 잘 아는 사람이라면, 아래 링크에 첨부된 방법들을 사용하면 좋을 것 같다.
(https://plein-de-verite.tistory.com/339)


*참고자료
1. https://blockdmask.tistory.com/338
2. https://chbuljumeok1997.tistory.com/42

 

Algorithm header에는?

- 코딩테스트를 준비하다보면, 필수적으로 정렬/순열 등을 사용해야 할 때가 있다.
- 이 때, <algorithm> header에 있는 함수 한 두개를 사용함으로써 위 요소들을 구현할 수 있다.
- C++ code에서 Algorithm header를 사용하기 위해서는 <algorithm> header를 include 해주면 된다.
- 코딩테스트 내에서는, 주로 vector container와 함께 사용되는 경우가 많다.


Algorithm header에 속한 함수들

* 대수 비교(min, max)

  - 두 수 중 큰 값(작은 값)을 return 해준다.

#include <stdio.h>
#include <algorithm>

using namespace std;

int main(){
  int i_A = 10;
  int i_B = 5;

  printf("max: %d\n", max(i_A, i_B)); // 10
  printf("min: %d\n", min(i_A, i_B)); // 5

  double d_A = 10.5;
  double d_B = 5.75;

  printf("max: %lf\n", max(d_A, d_B)); // 10.5
  printf("min: %lf\n", min(d_A, d_B)); // 5.75

  return 0;
}

 

 

* 정렬(sort)

- 기본(default)으로는 수들을 작은 것부터 큰 것으로 정렬하는 오름차순 정렬을 수행한다.
- 내림차순 정렬을 수행하기 위해서는 <functional>의 greater<int>()를 사용해야 한다.
- vector를 정렬하고 싶을 땐, sort 함수의 인자로 container의 시작과 끝 iterator 값을 넣어준다.
- Return value는 none이다.

#include <stdio.h>
#include <algorithm>
#include <vector>
#include <functional>

using namespace std;

int main(){
  // 오름차순 정렬
  vector<int> v1{7, 5, 4, 1, 3, 2, 6};
  sort(v1.begin(), v1.end()); // v1 = {1, 2, 3, 4, 5, 6, 7}

  for(int i=0; i<v1.size(); i++){
    printf("%d ", v1[i]);
  }
  printf("\n");

  // 내림차순 정렬
  vector<int> v2{7, 5, 4, 1, 3, 2, 6};
  sort(v2.begin(), v2.end(), greater<int>());

  for (int i = 0; i < v2.size(); i++) {
	  printf("%d ", v2[i]); // v2 = {7, 6, 5, 4, 3, 2, 1}
  }
  printf("\n");

  return 0;
}

 

 

* 순열(next_permutation, prev_permutation)

- next_permutation은 현재 vector(또는 array)로 표현된 순열의 다음 순열을 구해준다. (O(n))
- prev_permutation은 현재 vector(또는 array)로 표현된 순열의 이전 순열을 구해준다. (O(n))
- 위 함수들의 인자로 container의 시작과 끝 iterator 값이나 array의 주소값들을 넣어준다.
- 다음(이전) 순열이 있다면 true를, 없다면 false를 반환한다.

#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
  // next_permutation
  vector<int> v1 = {1, 2, 3, 4};

  do {
    for(int i=0; i<4; i++)
	  printf("%d ", v1[i]);
	printf("\n");
	/*
	  1 2 3 4
	  1 2 4 3
	  1 3 2 4
	  ...
	  4 2 3 1
	  4 3 1 2
	  4 3 2 1
	*/
  } while(next_permutation(v1.begin(), v1.end()));

  // prev_permutation
  vector<int> v2 = { 4, 3, 2, 1 };

  do {
	  for (int i = 0; i < 4; i++)
		  printf("%d ", v2[i]);
	  printf("\n");
	  /*
		4 3 2 1
		4 3 1 2
		4 2 3 1
		...
		1 3 2 4
		1 2 4 3
		1 2 3 4
	  */
  } while (prev_permutation(v2.begin(), v2.end()));

  return 0;
}

 

 

* 이분 탐색(binary_search)

- bool binary_search(ForwardIterator first, ForwardIterator last, const T& val);
- 위 함수들의 인자로 (1) container의 시작과 끝 iterator 값이나 (2) array의 주소값들을 넣어준다.
- 컨테이너(배열에) 찾고자 하는 값 val이 있다면 true를, 없다면 false를 반환한다.

#include <stdio.h>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
  vector<int> v = {10, 11, 12, 13, 14, 15, 16};
  int arr[7] = {20, 21, 22, 23, 24, 25, 26};

  int find_v = 14;
  int find_a = 21;

  if(binary_search(v.begin(), v.end(), find_v))
    printf("the value %d is found in vector v!\n", find_v);
  else
    printf("the value %d is not found in vector v!\n", find_v);

  if (binary_search(arr, arr+7, find_a))
	  printf("the value %d is found in array arr!\n", find_a);
  else
	  printf("the value %d is not found in array arr!\n", find_a);

  return 0;
}

 

Vector란?

- 크기를 가변적으로 변경할 수 있는 Dynamic Array(동적배열)이다.
- 속도적인 측면에서는, 기존 배열에 비해서 느릴 수 있지만, 메모리 관리에서 이점을 가지고 있다.


Vector 사용법

* 헤더파일
  - C++ code에서 vector를 사용하기 위해서는 <vector> header를 include해주어야 한다.

#include <vector>

 


* 선언 (Declaration)
- 기본적인 vector 선언법

vector<int> v;                    // empty vector
vector<int> v = {1, 2, 3, 4, 5};  // initialize with {1, 2, 3, 4, 5}
vector<int> v(5);                 // initialize with {0, 0, 0, 0, 0}
vector<int> v(5, 1);              // initialize with {1, 1, 1, 1, 1}

 

- 아래와 같이 2차원 벡터들을 선언하거나, element를 pair의 값으로 설정할 수도 있다.
- pair의 경우 <utility> header에 포함되어 있으나, <vector> header에도 역시 포함되어 있어 추가적인 header file은 필요없다.

vector<int> v[5];          // 2-dimensional vector
vector<vector<int> v;      // 2-dimensional vector
vector<pair<int, char>> v; // store pair<int, char> value

 

* 사용법

1. vector에서의 위치 지칭 및 vector의 크기 구하기
- vector의 indexing은 기존의 array와 마찬가지로 0부터 시작한다.

vector<int> v = {1, 2, 3, 4, 5};

int front_val = v.front();  // front_val: 1
int back_val = v.back();    // back_val: 5

vector<int>::iterator begin_it = v.begin(); // first element를 가르키는 iterator
vector<int>::iterator end_it = v.end();     // last element 다음을 가르키는 iterator

int v_len = v.size(); // size of vector (5)

int first_val  = v[0];      // first_val: 1
int first_val2 = v.at(0);   // first_val2: 1
int second_val  = v[1];     // second_val: 2
int second_val2 = v.at(1);  // second_val2: 2
int last_val  = v[4];           // last_val: 5
int last_val2 = v[v_len-1];     // last_val2: 5
int last_val3 = v.at(4);        // last_val3: 5
int last_val4 = v.at(v_len-1);  // last_val4: 5

 

2. vector에서의 값 추가 및 삭제

vector<int> v;

/* vector에서의 값 추가 */
v.push_back(3); // v의 맨 뒤에 3을 추가. v: {3}
v.push_back(4); // v의 맨 뒤에 4을 추가. v: {3, 4}
v.push_back(5); // v의 맨 뒤에 5을 추가. v: {3, 4, 5}
v.insert(v.begin(), 2);      // v.begin()에 2를 추가. v: {2, 3, 4, 5}
v.insert(v.begin(), 2, 1);   // v.begin()에 1을 2번 추가. v: {1, 1, 2, 3, 4, 5}
v.insert(v.begin()+2, 2, 6); // 2번째 element 위치에 6을 2번 추가. v: {1, 1, 6, 6, 2, 3, 4, 5}  


/* vector에서의 값 삭제 */
v.pop_back(); // v의 마지막 element를 제거. v: {1, 1, 6, 6, 2, 3, 4}
v.erase(v.begin()+1); // begin(1)+1 = 2번째 원소(index: 1)를 제거. v:{1, 6, 6, 2, 3, 4}
v.erase(v.begin()+1, v.begin()+2); // 2번쨰 원소만을 제거. v: {1, 6, 2, 3, 4}
v.erase(v.begin()+1, v.begin()+3); // 2,3번째 원소를 제거. v: {1, 3, 4}

v.clear(); // v의 모든 element를 제거. v: {}

 

3. 그 밖에 vector에서 사용되는 유용한 functions

vector<int> v = {1, 2, 3, 4, 5};

long long v_max_size = v.max_size();  // vector가 수용할 수 있는 최대 개수
bool empty_flag = v.empty(); // vector가 비었으면 1, element가 하나라도 있으면 0
v.resize(3);    // v의 크기를 3으로 설정. v: {1, 2, 3}
v.resize(5, 7); // v의 크기를 5로 설정하고, 추가 element들은 0으로 초기화. v: {1, 2, 3, 7, 7}