C++/C++ : 코팅테스트 합격자 되기(인프런)

4장/5장 반드시 알아야 할 C++ 문법

더블유제이플로어 2024. 10. 6. 17:22

https://www.inflearn.com/course/cpp-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%A9%EA%B2%A9/dashboard

 

[지금 무료] 코딩 테스트 합격자 되기 - C++ 강의 | dremdeveloper - 인프런

dremdeveloper | 코딩 테스트 합격을 위한 C++ 강의, 책 없이도 가능! 저자와 직접 소통 가능한 커뮤니티 제공!, [사진]여기에 문의 하세요https://open.kakao.com/o/gX0WnTCf📘 코딩 테스트 합격자 되기 - C++편

www.inflearn.com

목차

◆  빌트인 데이터 타입
◆  배열 / 문자열
◆  STL

  ●  반복
  ●  컨테이너
  ●  알고리즘


◆  빌트인 데이터 타입 (정의)

  ● 언어 자체에서 제공하는 변수 타입
  ● C++은 변수 선언 시 타입을 명시해야 한다. (정적캐스팅)
  ● 정수형, 부동소수형, 논리형, 문자형, 배열형 등이 있다.

파이썬은 변수 타입을 명시하지 않아도 되나, (동적캐스팅)

C++는 변수 선언 시 타입을 명시해야만 한다. (정적캐스팅)

https://github.com/dremdeveloper/codingtest_cpp/blob/main/reference/variable.cpp

 

codingtest_cpp/reference/variable.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

    // 1. 기본 데이터 타입 변수
    int a = 10;        // 정수형 변수
    double b = 20.5;   // 실수형 변수
    char c = 'A';      // 문자형 변수
    bool d = true;     // 불리언 변수 (참/거짓)

double vs float 중에서 double을 많이 쓴다.
double과 float의 차이점은 
소수점의 정밀도가 double 이 더 높다.
코딩테스트에서 실수형 변수는 double을 사용한다.

문자형 변수 char는 작은따옴표(' ')로  문자를 감싼다.
문자는 한 글자만 올 수 있다.
char c = 'AB'; 이렇게는 선언이 안된다.
작은따옴표가 감싼 문자는 하나만 가능하다.

초기화를 할 때 주의할 것
큰 따옴표(" ")로 해서 문자열을 초기화할 수 있다.

불리언 변수에는 참이면 true / 거짓이면 false로 된다.
4개의 변수를 가장 많이 사용한다. ( int / double / char / bool )

    /* 
    변수란?
    - 변수는 메모리상에 값을 저장하는 공간을 의미합니다.
    - 변수를 선언할 때는 해당 변수의 타입을 지정해야 하며, 그 타입에 따라 저장할 수 있는 값의 종류와 크기가 결정됩니다.
    - 변수는 프로그램 내에서 데이터를 저장하고 참조하기 위해 사용됩니다.
    */

int a = 3.14;

이렇게 선언한다면
int는 정수 타입이고 , 3.14는 실수 타입이라서
a를 출력하면 3이 나온다.
3.14를 저장할 수 없다.

변수를 선언할 때 어떤 값을 담을지, 어느 정도 크기를 담을지 생각해야 한다.
크기는 코딩테스트에서는 크게 고려 안 해도 되지만, 
각 변수의 타입마다 변수가 차지하는 크기가 결정된다.
int - 4byte 등등.

    // 2. 변수의 초기화
    int uninitialized;     // 초기화되지 않은 변수
    int initialized = 100; // 초기값을 가진 변수

초기화를 안 할 수도 있고, 초기화를 할 수 있다.
되도록이면 초기값을 가진 변수로 선언하는 걸 추천한다.
초기화를 안 한 변수에는 어떤 값이 들어가 있는지 모른다.
그걸 쓰레기값이라고 한다.

시험 볼 때 초기화를 하지 않으면 실수하는 경우가 많다.
생각하지도 못한 값이 들어있기 때문에 실수하는 경우가 많다.

    /* 
    주의사항:
    - 초기화되지 않은 변수는 그 안에 쓰레기 값이 들어있을 수 있으므로 사용하기 전에 반드시 초기화하는 것이 좋습니다.
    - C++에서는 변수를 선언하면서 동시에 초기화할 수 있습니다. 이렇게 하면 나중에 실수로 초기화를 잊어버리는 문제를 예방할 수 있습니다.
    */

    cout << "Value of a: " << a << endl;        // 출력: 10
    cout << "Value of b: " << b << endl;        // 출력: 20.5
    cout << "Value of c: " << c << endl;        // 출력: A
    cout << "Value of d: " << d << endl;        // 출력: 1 (true는 1로, false는 0으로 출력됩니다.)

선언한 변수를 cout으로 출력할 수 있다.
cout  이 표준 출력이기 때문에
true값은 1로 false 값은 0으로 출력된다.
endl는 endline이다.
출력 후에 다음 한 줄 띄우겠다는 의미이다.


◆  빌트인 데이터 타입 (변수의 크기)

QR Code

https://github.com/dremdeveloper/codingtest_cpp/blob/main/reference/sizeof.cpp

 

codingtest_cpp/reference/sizeof.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

sizeof 연산자는

sizeof(타입) / sizeof(변수명) 명시할 수 있다.

    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    std::cout << "Size of short: " << sizeof(short) << " bytes" << std::endl;
    std::cout << "Size of long: " << sizeof(long) << " bytes" << std::endl;
    std::cout << "Size of long long: " << sizeof(long long) << " bytes" << std::endl;
    std::cout << "Size of float: " << sizeof(float) << " bytes" << std::endl;
    std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
    std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;
    std::cout << "Size of bool: " << sizeof(bool) << " bytes" << std::endl;

(변수 타입)이다.

    // 포인터 타입의 크기 출력
    int* ptr = nullptr;
    std::cout << "Size of pointer: " << sizeof(ptr) << " bytes" << std::endl;

    // 사용자 정의 데이터 타입(구조체)의 크기 출력
    struct MyStruct {
        int i;
        double d;
        char c;
    };
    std::cout << "Size of MyStruct: " << sizeof(MyStruct) << " bytes" << std::endl;

코딩테스트에서는 크게 몰라도 되나, 개념 정리를 위해서 알아두면 좋다.

    // 주석으로 변수 타입과 크기를 테이블 형식으로 정리
    /*
    | Variable Type | Size in 32-bit (bytes) | Size in 64-bit (bytes) |
    |---------------|------------------------|------------------------|
    | int           | 4                      | 4                      |
    | short         | 2                      | 2                      |
    | long          | 4                      | 8                      |
    | long long     | 8                      | 8                      |
    | float         | 4                      | 4                      |
    | double        | 8                      | 8                      |
    | char          | 1                      | 1                      |
    | bool          | 1                      | 1                      |
    | pointer       | 4                      | 8                      |
    | MyStruct      | 12                     | 16                     |
    */

외울 필요는 없으나 한번 확인해 볼 만한 점은
long , pointer  같은 경우는 32bit 환경이랑 64bit 환경에서 크기가 다르다.
개발할 때 환경에 따라 크기가 다를 수 있어서 정리를 하였다.
long - (32bit) 4byte   (64bit) 8byte
pointer - (32bit) 4byte    (64bit) 8byte
MyStruct(구조체) - (32bit) 12byte    (64bit) 16byte
다른 변수타입은  32bit나 64bit나 동일하다.


◆  빌트인 데이터 타입 (배열)

  ● 동일한 타입의 변수를 묶어서 사용하는 자료구조

  ● 임의 접근을 통해 특정 위치 원소에 빠르게 접근 가능하다.

  ● 임의 위치에서 원소를 삽입해야 하는 경우 O(N), 맨 뒤에 원소를 삽입하는 경우 O(1)

학생들을 관리하는 프로그램을 작성할 때
되게 많은 인원을 관리하는 경우 어떻게 해야 효율적으로 진행되는지 알아보자.
예를 들어 1000명이라면
int a, int b, int c....
int 형 변수 1000개를 만들어야 한다면

문제점
  1. 코드의 양이 많아진다.
  2. 관리하기 어려워진다.

변수의 의미를 부여해서 a는 첫 번째, b는 두 번째 c는 세 번째 등등 이지만
프로그래밍적으로는 변수 a , 변수 b, 변수 c는 모두 관련이 없다.
그렇기에 관리하기가 어렵다.
관련이 높은 변수들은 하나로 묶어서 관리를 하는 게 어떨까.
그럴 때 가장 적절한 것은 배열이다.

배열은 동일한 타입의 변수를 묶어서 사용하는 자료구조이다.
int a = 5;
정수형 변수 5개를 한 번에 받을 경우는
int a [5];
a [0] a [1] a [2] a [3] a [4]

배열이 선언되었다.
인덱스를 통해서 임의의 공간에 접근할 수 있는 거를
임의 접근이라고 한다.

배열은 index를 통해서 접근하는 걸 제공하기 때문에
빠르게 접근할 수 있다.

	for (int i = 0; i < 5; i++)
	{
		a[i] = 30;
	}

이렇게 반복문 하나를 통해서 배열을 제어할 수 있다.

따로 변수를 선언하였을 때는 불가능하나 배열은 가능하다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/reference/array.cpp

 

codingtest_cpp/reference/array.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

    /* 
    배열(Array)이란?
    - 배열은 동일한 타입의 여러 데이터를 연속적인 메모리 공간에 저장할 수 있는 데이터 구조입니다.
    - 배열의 각 요소는 인덱스를 통해 접근할 수 있습니다.
    - 배열의 크기는 선언 시에 지정되며, 이후 크기 변경이 불가능합니다.
    - 배열은 스택 메모리에 할당됩니다(동적으로 힙 메모리에 할당하려면 동적 배열을 사용해야 합니다).
    */

    int arr[5] = {1, 2, 3, 4, 5};  // 크기가 5인 int 타입의 배열 선언 및 초기화

    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";  // 배열의 요소 접근 및 출력
    }
    cout << endl;  // 출력: 1 2 3 4 5

    /* 
    주의사항:
    1. 배열의 범위를 벗어나는 인덱스에 접근하면 정의되지 않은 동작(Undefined Behavior)이 발생합니다.
    */
    // arr [10] = 50;  // 오류: 배열의 범위를 벗어난 인덱스에 접근

    /* 
    2. 배열의 크기를 동적으로 할당하려면 new와 delete 연산자를 사용해야 합니다.
    */

delete를 해주지 않으면 메모리 공간이 계속 쌓이게 된다.
메모리 공간도 물리적인 공간이기 때문에 한계가 있어
메모리 공간을 쓸 수 없게 된다.
동적 배열을 쓰게 될 경우라면 사용이 다 끝난 배열은 delete를 꼭 써줘야 한다.

    /* 
    3. 배열의 크기는 컴파일 시에 결정되어야 하므로, 실행 시간에 크기를 지정할 수 없습니다.
    이런 경우 동적 배열을 사용해야 합니다.
    */

왜 이런 게 필요하냐,
메모리 효율 때문이다.
예를 들어 학생을 관리하는 시스템을 만드는데
학생이 30명 있는 학교도 , 2만 명이 있는 학교도
모든 학교에서 돌아가는 시스템을 만들고 싶다면
학생정보를 담을 공간이 필요하다.
정적배열로 사용하는 경우에는 가장 학생이 많은 인원수를 기준으로 잡아야 한다.
프로그램과 시작되는 동시에 배열의 크기를 지정해야 하기 때문에
가장 많은 학생수로 기준을 잡아야 한다.
학생수가 30명 있는 학교에서는 메모리 공간 낭비를 하게 된다.
그래서 이런 경우는 동적 배열을 사용한다.
동적 배열은 실행하면서 배열의 크기를 정할 수 있기 때문에 효율적으로 사용할 수 있다.

다만, 주의사항이 있다.
new 연산자과 delete연산자를 꼭 사용해야 한다.

    /* 
    4. 배열은 할당된 크기 변경이 불가능합니다. 크기 변경이 필요한 경우, 새로운 크기의 배열을 만들고 데이터를 복사해야 합니다.
    */


◆  빌트인 데이터 타입 (배열의 성능) 

맨 앞에 삽입하면 시간 복잡도는 O(N)이다.
기존에 있었던 배열들을 뒤로한 칸씩 옮겨야 하기 때문이다.
맨 뒤에 삽입하게 되면 시간 복잡도는 O(1)이다.
이러한 특성을 알고 있어야, 추후에 테스트에서 배열을 사용할지
다른 걸 사용할지 결정하게 된다.


◆  빌트인 데이터 타입 (배열의 성능) 

  ● 문자의 집합
  ● C++에서는 문자열을 편리하게 사용할 수 있는 여러 가지 메서드를 제공한다.

큰 따옴표 사용하는 것은 문자열이라고 생각할 수 있다.
<string> 헤더파일을 선언해야 쓸 수 있다.
string str1 = "Hello, world!"에서 문자열을 보면

H|e|l|l|o|,| |w|o|r|l|d|!

0|1|2|3|4|5|6|7|8|9|10|11|12

내부에서는 이렇게 문자들이 저장되어 있다.
substr(0,5)라는 것은 배열 인덱스 0부터 5개까지 반환한 함수이다.
string str4(5, 'A'); 를 하게 되면
A를 5개 선언한다.
문자열은 자주 사용하는 메서드임을 알고 있자.


◆  문자열의 메서드

  ● 초기화
  ● replace (문자열 대체)
  ● + operator (문자열 추가)
  ● substr (부분 문자열)
  ● find(문자열 검색)
  ● length(문자열의 길이)

https://github.com/dremdeveloper/codingtest_cpp/blob/main/reference/string.cpp

 

codingtest_cpp/reference/string.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

    // 1. 기본적인 문자열 생성 및 출력
    string str1 = "Hello, World!";  // 문자열 초기화
    cout << str1 << endl;           // 출력: Hello, World!
    // 인덱스:  | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
    // 값    :  | H| e| l| l| o| ,|  | W| o| r| l| d|!|
    // 시간 복잡도: O(n) (n은 문자열의 길이)
    // 주의할 점: 문자열의 길이가 길어질수록 초기화에 시간이 더 걸립니다.

    // 여러 가지 문자열의 초기화 방법
    string str2("Hello");           // str2: H e l l o
    cout << str2 << endl;           // 출력: Hello
    // 인덱스:  | 0| 1| 2| 3| 4|
    // 값    :  | H| e| l| l| o|
    // 시간 복잡도: O(n)

    string str3 = "World";          // str3: W o r l d
    cout << str3 << endl;           // 출력: World
    // 인덱스:  | 0| 1| 2| 3| 4|
    // 값    :  | W| o| r| l| d|
    // 시간 복잡도: O(n)

객체 지향 함수에서 생성자의 개념과 접목시킬 수 있다.

    string str4(str2 + ", " + str3 + "!"); // str4: H e l l o ,   W o r l d!
    cout << str4 << endl;                  // 출력: Hello, World!
    // 인덱스:  | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
    // 값    :  | H| e| l| l| o| ,|  | W| o| r| l| d|!|
    // 시간 복잡도: O(n + m) (n과 m은 각각 str2와 str3의 길이)
    // 주의할 점: 문자열을 자주 연결하면 시간 복잡도가 커질 수 있습니다.

+ 연산자를 이용하여 문자열을 연결할 수 있다.

    string str5 = str1;             // str5: H e l l o ,   W o r l d!
    cout << str5 << endl;           // 출력: Hello, World!
    // 인덱스:  | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
    // 값    :  | H| e| l| l| o| ,|  | W| o| r| l| d|!|
    // 시간 복잡도: O(n)

문자열은 대입 연산자를 쓸 수 있다.

    string str6(5, 'A');            // str6: A A A A A
    cout << str6 << endl;           // 출력: AAAAA
    // 인덱스:  | 0| 1| 2| 3| 4|
    // 값    :  | A| A| A| A| A|
    // 시간 복잡도: O(n)

string str(int, char) 형태로도 초기화할 수 있다.

    /* 
    문자열(string)이란?
    - C++의 STL에서 제공하는 문자열 클래스입니다.
    - 문자의 연속으로 구성되며, '\0' 문자로 종료되지 않습니다 (C 스타일 문자열과 차이점).
    - + 연산자를 통해 문자열을 연결할 수 있습니다.
    - 내부적으로는 동적 배열로 구현되어 있어 크기를 동적으로 변경할 수 있습니다.
    */

    // 2. 문자열 연결

`    // 3. 문자열 길이와 접근
    cout << "Length of str1: " << str1.length() << endl;  // 출력: 13
    // str1의 길이: 13 (H e l l o ,   W o r l d!)
    // 시간 복잡도: O(1)

    cout << "First character of str1: " << str1 [0] << endl;  // 출력: H
    // str1의 첫 번째 문자: H (H e l l o ,   W o r l d!)
    // 시간 복잡도: O(1)

배열처럼 인덱스 접근이 가능하다.

    // 4. 문자열의 부분 문자열 및 찾기
    string str10 = str1.substr(7, 5);  
    cout << "Substring: " << str10 << endl;  // 출력: World
    // 인덱스:  | 0| 1| 2| 3| 4|
    // 값    :  | W| o| r| l| d|
    // 시간 복잡도: O(m) (m은 부분 문자열의 길이)
    // 주의할 점: substr의 인자는 시작 위치와 길이입니다.

변수. substr(int a , int b); 는

a 번째 index부터 자기 자신을 포함한 b 수만큼 문자열을 구성해서 반환한다.

    size_t pos = str1.find("World");  
    if (pos!= string::npos) {
        cout << "\"World\" starts at index " << pos << endl;  // 출력: "World" starts at index 7
    } else {
        cout << "\"World\" not found!" << endl;
    }
    // str1:   | H| e| l| l| o| ,|  | W| o| r| l| d|!|
    // 인덱스: | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
    // 시간 복잡도: O(n)
    // 주의할 점: find 메서드는 찾는 문자열의 시작 위치를 반환하며, 없으면 string::npos를 반환합니다.

. find("문자열")

을 하여 검색하는데 문자열이 없는 경우는

string::npos를 반환한다.

찾는 경우에는 찾는 문자의 첫 번째 문자 인덱스를 출력한다.

    // 5. 문자열 대체
    string str11 = "I like cats";     
    cout << str11 << endl;            // 출력: I like cats
    // 인덱스:  | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
    // 값    :  | I|  | l| i| k| e|  | c| a| t| s|

    str11.replace(7, 4, "dogs");  
    cout << str11 << endl;  // 출력: I like dogs
    // str11:  | I|  | l| i| k| e|  | d| o| g| s|
    // 인덱스: | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
    // 시간 복잡도: O(m) (m은 대체할 문자열의 길이)
    // 주의할 점: replace의 인자는 시작 위치, 길이, 대체할 문자열입니다.

문자열. replace(int a , int b, string c);
a 번째 인덱스부터 b만큼 수를 가진 문자열을 c로 변경하는 함수이다.
(와. C#과 동일하다.)


◆  STL

  ● C++에서 제공하는 템플릿기반의 표준 라이브러리
  ● 코딩 테스트에서는 컨테이너, 알고리즘을 중점적으로 학습해야 한다.
  ● 반복자를 통해 모든 알고리즘/컨테이너를 동일한 방법으로 제어 가능하다.

꼭 알아야 할 STL을 작성하였다.
프로그램 100문제 푸는 동안 사용했던 STL을 정리해서 집필하였다.
현재 나와있는 STL은 익히고 알자.
3가지가 있다.

반복자 / 컨테이너 / 알고리즘이다.

컨테이너는 어떻게 데이터를 담을지 관점을 가지고 있다.
vector는 배열과 비슷하다.
set 은 중복값을 허용하지 않는 자료구조.
데이터 관리를 위한 자료구조라고 보면 된다.

vector / set / map / unordered_map / unordered_set

내부 구현은 다 다를 것이다.
컨테이너마다 문법이 다 따로따로면 사용하기 어렵다.
모든 컨테이너에 상관없이 동일한 방법으로 접근할 수 있게 역할을 해주는 게 반복자이다.
반복자는 vector에 접근할 때나 set에 접근할 때나
동일한 문법으로 접근할 수 있다.

반복자가 필요하고, 컨테이너는 자료구조이며,알고리즘 같은 경우는 
컨테이너를 통해서 동작을 할 때 사용한다.
컨테이너에서 특정 개수를 세거나(count) 정렬을 하거나 (sort)
수열을 구하거나 (next_permutation) 중복값을 제거하거나 (unique)
이진 탐색을 하거나 (binary_search) 
최대/ 최솟값을 찾을 경우 (max_element / min_element) 사용하는 알고리즘이다.


◆  STL(반복자)

  ● 특정 자료구조 / 알고리즘에 종속되지 않고 동일하게 순회 가능하다.
  ● 코딩테스트에서는 순방향 / 역방향 반복자를 알아두어야 한다.
  ● 순방향반복자의 시작은 begin() 끝은 end()
  ● 역방향 반복자의 시작은 rbegin() 끝은 rend()

end()와 rend()는 유효하지 않은 영역이다.

반복자는 특정 자료구조 / 알고리즘에 종속되지 않고 순회가 가능하기에
순방향 / 역방향 반복자는 반드시 알아둬야 한다.


◆  STL(반복자의 실제 동작)

for문에서. end()를 포함하지 않는다. 이유는 유효하지 않는 영역이기 때문이다.
보통 이런 경우가 많다.

첫 번째 인자는 begin() 두 번째 인자는 end()를 넘기면
순방향으로 순환하면서 컨테이너에서 동작을 한다.
실제로 end()는 어떤 것을 하는 영역에 포함되지 않는다는 걸 기억해 둔다.
보통 컨테이너에서 값을 찾다가 못 찾을 때 end()를 반환한다.
왜냐면 값을 찾았다면 컨테이너에서 찾은 위치의 인덱스를 반환할 텐데
못 찾았다면 컨테이너의 특정 위치나 값을 반환하면 안 되기 때문에
못 찾았다는 걸 식별할 수 있는. end() 영역을 반환한다.
역방향은 r을 쓴다. r은 reverse의 약어이다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/iterator.cpp

 

codingtest_cpp/STL/iterator.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

// 반복자 예시
// 반복자는 컨테이너(예: 벡터, 리스트 등)의 요소를 순회하는 데 사용됩니다.
// 반복자는 포인터와 비슷하게 동작하며, STL에서 중요한 역할을 합니다.
// 반복자를 사용하면 컨테이너의 내부 구현에 독립적으로 요소를 처리할 수 있습니다.

#include <iostream>
#include <vector>
#include <list>

using namespace std;

// 반복자 예시
// 반복자는 컨테이너(예: 벡터, 리스트 등)의 요소를 순회하는 데 사용됩니다.
// 반복자는 포인터와 비슷하게 동작하며, STL에서 중요한 역할을 합니다.
// 반복자를 사용하면 컨테이너의 내부 구현에 독립적으로 요소를 처리할 수 있습니다.

int main() {
    // 벡터 선언 및 초기화
    vector<int> vec = {1, 2, 3, 4, 5};
    
    // 리스트 선언 및 초기화
    list<int> lst = {10, 20, 30, 40, 50};
    
    // 순방향 반복자를 사용하여 벡터의 요소를 순회하고 출력
    cout << "Vector elements: ";
    for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " "; // 반복자가 가리키는 요소를 출력
    }
    cout << endl; // 출력: Vector elements: 1 2 3 4 5

    // 순방향 반복자를 사용하여 리스트의 요소를 순회하고 출력
    cout << "List elements: ";
    for (list<int>::iterator it = lst.begin(); it != lst.end(); ++it) {
        cout << *it << " "; // 반복자가 가리키는 요소를 출력
    }
    cout << endl; // 출력: List elements: 10 20 30 40 50

    // 역방향 반복자를 사용하여 벡터의 요소를 순회하고 출력
    cout << "Vector elements in reverse: ";
    for (vector<int>::reverse_iterator rit = vec.rbegin(); rit != vec.rend(); ++rit) {
        cout << *rit << " "; // 역방향 반복자가 가리키는 요소를 출력
    }
    cout << endl; // 출력: Vector elements in reverse: 5 4 3 2 1

    // 역방향 반복자를 사용하여 리스트의 요소를 순회하고 출력
    cout << "List elements in reverse: ";
    for (list<int>::reverse_iterator rit = lst.rbegin(); rit != lst.rend(); ++rit) {
        cout << *rit << " "; // 역방향 반복자가 가리키는 요소를 출력
    }
    cout << endl; // 출력: List elements in reverse: 50 40 30 20 10

    return 0;
}

벡터와 리스트는 컨테이너는 다르지만 반복자를 이용하여
동일한 문법으로 순회할 수 있다.

/*
반복자의 목적:
- 반복자는 컨테이너의 요소를 순차적으로 접근하고 조작하는 데 사용됩니다.
- 반복자를 통해 컨테이너의 내부 구현에 독립적으로 요소를 처리할 수 있습니다.

반복자의 장점:
1. 유연성: 다양한 컨테이너에 대해 동일한 방식으로 요소를 순회할 수 있습니다.
2. 추상화: 컨테이너의 내부 구조를 몰라도 요소를 접근할 수 있습니다.
3. 범용성: 알고리즘 함수와 함께 사용하여 코드의 재사용성을 높입니다.

순방향 반복자와 역방향 반복자:
1. 순방향 반복자: 컨테이너의 처음(begin)부터 끝(end)까지 순차적으로 요소를 접근합니다.
    - 선언: vector <int>::iterator it;
    - 초기화: it = vec.begin();
    - 사용: *it를 통해 반복자가 가리키는 요소에 접근합니다.

2. 역방향 반복자: 컨테이너의 끝(rbegin)부터 처음(rend)까지 역순으로 요소를 접근합니다.
    - 선언: vector <int>::reverse_iterator rit;
    - 초기화: rit = vec.rbegin();
    - 사용: *rit를 통해 역방향 반복자가 가리키는 요소에 접근합니다.

반복자의 사용방법:
1. 반복자 선언: 컨테이너 타입에 따라 반복자를 선언합니다. 예: vector <int>::iterator.
2. 반복자 초기화: begin()과 end(), 또는 rbegin()과 rend() 함수를 사용하여 반복자를 초기화합니다.
3. 반복자 사용: 반복자를 사용하여 요소에 접근하고 조작합니다.

예시:
- 벡터 요소 순회: for (it = vec.begin(); it!= vec.end(); ++it).
- 리스트 요소 순회: for (it = lst.begin(); it!= lst.end(); ++it).
- 역방향 요소 순회: for (rit = vec.rbegin(); rit!= vec.rend(); ++rit).
- 요소 접근: *it 또는 *rit를 사용하여 반복자가 가리키는 요소에 접근합니다.

코드 설명:
1. 벡터 선언 및 초기화: vector <int> vec = {1, 2, 3, 4, 5}; 벡터를 선언하고 초기화합니다.
2. 리스트 선언 및 초기화: list <int> lst = {10, 20, 30, 40, 50}; 리스트를 선언하고 초기화합니다.
3. 순방향 반복자 사용:
    - for (vector <int>::iterator it = vec.begin(); it!= vec.end(); ++it): 순방향 반복자를 사용하여 벡터의 요소를 순회합니다.
    - for (list <int>::iterator it = lst.begin(); it!= lst.end(); ++it): 순방향 반복자를 사용하여 리스트의 요소를 순회합니다.
    - *it: 순방향 반복자가 가리키는 요소를 출력합니다.
4. 역방향 반복자 사용:
    - for (vector <int>::reverse_iterator rit = vec.rbegin(); rit!= vec.rend(); ++rit): 역방향 반복자를 사용하여 벡터의 요소를 역순으로 순회합니다.
    - for (list <int>::reverse_iterator rit = lst.rbegin(); rit!= lst.rend(); ++rit): 역방향 반복자를 사용하여 리스트의 요소를 역순으로 순회합니다.
    - *rit: 역방향 반복자가 가리키는 요소를 출력합니다.
*/


◆  STL(컨테이너)

  ● STL에서 제공하는 데이터를 저장하고 관리하는 저장소.
  ● 각 컨테이너 메서드의 시간복잡도를 정리하는 게 중요하다.
  ● 비슷한 동작을 하나 시간복잡도가 다른 경우 정리가 필요하다.
  ● 각 알고리즘에 맞는 자료구조를 학습하는 게 필요하다.


◆  STL(컨테이너-> vector)

  ● 배열처럼 사용할 수 있는 컨테이너 (임의접근가능)
  ● 원소의 수에 따라 내부 배열 크기가 자동으로 증가 (신경 쓰지 않아도 된다) 
  ● 맨 앞 혹은 중간에 원소를 삽입하는 경우 비효율적이다. O(N)
  ● 맨 뒤에 원소를 삽입하는 경우 효율적이다. O(1)

정적 배열 VS 동적 배열

정적 배열은 프로그램을 실행할 때 크기가 고정된다.
동적 배열은 프로그램이 실행될 때 new , delete 연산자로 크기를 제어할 수 있다.
다만 메모리 해제도 직접 해줘야 하기 때문에 반드시 사용자가 delete 연산자를 써서 메모리 해제 해야 한다.
vector는 동적 배열이긴 하나 내부에서 new, delete를 자동으로 해준다.
내부에서 공간이 필요한 경우 알아서 확장이 되기 때문에 정적 배열처럼 메모리를 관리할 필요 없다.
배열이 필요한 경우는 vector를 쓰면 된다.
vector는 배열의 특징을 가지고 있기 때문이다.

많이 쓰는 메서드는
push_back() - 맨 끝에 원소 추가.
만일 vector가 맨 앞의 원소 삽입이 빨랐다면 push_front()도 제공했을 텐데 메서드 제공하지 않는다.
pop_back() - 마지막 원소를 제거.
어쩔 수 없이 지정한 위치의 원소 삽입 및 원소 제거를 할 경우에는
insert() erase() 메서드를 사용한다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/vector.cpp

 

codingtest_cpp/STL/vector.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	// 벡터 초기화 방법 1 : 기본 생성자
	vector<int> vec1; // 빈 벡터 선언 : vec1 = {}

	// 벡터 초기화 방법 2 : 크기 지정, 모든 원소 0으로 초기화
	vector<int> vec2(5); // ver2 = { 0,0,0,0,0 };

	// 벡터 초기화 방법 3 : 크기와 초기값 지정
	vector<int> vec3(5, 1); // ver3 = { 1,1,1,1,1 };

	// 벡터 초기화 방법 4 : 초기화 리스트 사용
	vector<int> vec4 = { 1,2,3,4,5 }; // vec4 = { 1,2,3,4,5 };

	// 벡터 초기화 방법 5 : 다른 벡터로부터 초기화
	vector<int> vec5(vec4); // vec5 = { 1,2,3,4,5 };

	// 벡터 초기화 방법 6 : 다른 벡터로부터 초기화
	vector<int> vec6(vec4.begin() + 1, vec4.end() - 1); // vec6 = { 2,3,4 };

}

vec1와 vec2의 차이점이 있다.
vec2는 크기를 지정하였기 때문에 vec2 [3] = 0; 선언이 가능하지만
vec1 은 크기를 지정하지 않았기에 vec1 [3] = 0; 선언하면 error 뜬다.

vec1[3] = 0; 선언 후 에러메서지

	// 백터 메서드 예시
	vector<int> vec;

	// push_back : 벡터의 끝에 원소를 추가합니다.
	// vec.push_back(값);
	// 백터의 맨 끝에 '값'을 추가합니다.
	// 시간 복잡도 : 평균 O(1)
	vec.push_back(10); // vec = { 10 };
	vec.push_back(20); // vec = { 10,20 };
	vec.push_back(30); // vec = { 10,20,30 };

	// pop_back : 벡터의 마지막 원소를 제거합니다.
	// vec.pop_back();
	// 벡터의 맨 끝에 있는 원소를 제거합니다.
	// 시간 복잡도 : O(1)
	vec.pop_back(); // vec = { 10,20 }

	// insert : 지정한 위치에 원소를 삽입합니다.
	// vec.insert(위치, 값);
	// '위치'에 '값'을 삽입합니다. '위치'는 반복자로 지정합니다.
	// vec.begin()은 첫 번째 원소를 가리킵니다.
	// vec.begin() + 1 은 두 번째 원소를 가리킵니다.
	// 시간 복잡도 : O(N)
	vec.insert(vec.begin() + 1, 15); // vec = { 10,15,30 };

	// erase : 지정한 위치에 원소를 삽입합니다.
	// vec.erase(위치);
	// '위치'의 원소를 제거합니다. '위치'는 반복자로 지정합니다.
	// vec.begin()은 첫 번째 원소를 가리킵니다.
	// 시간 복잡도 : O(N)
	vec.erase(vec.begin()); // vec = { 15,30 };

	// size : 벡터의 크기를 반환합니다.
	// vec.size()
	// 현재 벡터에 저장된 원소의 개수를 반환합니다.
	// 시간복잡도 : O(1)
	cout << "Size of vector: " << vec.size() << endl; // Size of vector : 2
	// vector를 사용해야 하는 경우
	// 1. 동적 배열이 필요한 경우
	// ex. 프로그램 실행 중에 배열의 크기를 변경해야 하는 경우
	vector<int> dynamicArray;
	for (int i = 0; i < 10; ++i)
	{
		dynamicArray.push_back(i * 2); // {0, 2, 4, 6, 8, 10, 12, 14, 16, 18};
	}
	cout << "Dynamic array: ";
	for (int v : dynamicArray) {
		cout << v << " ";
	}
	cout << endl;

	// 2. 임의 접근이 필요한 경우
	// ex. 특정 인덱스에 빠르게 접근해야 하는 경우
	cout << "Third element: " << dynamicArray[2] << endl; // Third element: 4

	// 3. 데이터의 크기가 자주 바뀌는 경우
	// ex. 데이터의 추가와 삭제가 빈번하게 발생하는 경우
	vector<int> flexibleArray;
	flexibleArray.push_back(1); // {1}
	flexibleArray.push_back(2); // {1,2}
	flexibleArray.pop_back(); // {1}
	// vector를 사용하지 말아야 하는 경우
	// 1. 값을 자주 찾아야 할 때
	// ex. 원소의 존재 여부를 자주 검사해야 하는 경우에는 비효율적
	// 대안 std::set 또는 std::unordered_set 사용한다
	vector<int> searchVector = { 1,2,3,4,5 };
	if (find(searchVector.begin(), searchVector.end(), 3) != searchVector.end()) {
		cout << "3 is in the vector" << endl;
	}

	// 2. 맨 앞에 원소를 추가해야 할 때
	// ex. 벡터의 맨 앞에 원소를 자주 삽입해야 하는 경우에는 비효율적
	// 대안 std::deque 또는 std::list 사용한다
	vector<int> inefficientFrontInsert = { 1,2,3,4,5 };
	inefficientFrontInsert.insert(inefficientFrontInsert.begin(), 0); // { 0,1,2,3,4,5 }

◆  STL(컨테이너-> set)

  ● 중복을 허용하지 않는 순서가 없는 집합
  ● 원소가 자동으로 정렬됨 (균형이진트리오 동작함)
  ● 삽입 / 삭제 / 탐색 :O(logN)
  ● 중복을 허용하지 않거나 / 원소를 삽입과 동시에 정렬해야 하는 경우 효율적
  ● 삽입 / 삭제 / 탐색 시 자동 정렬되므로 정렬이 필요하지 않은 경우 비효율적

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/set.cpp

 

codingtest_cpp/STL/set.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

#include <iostream>
#include <set>

using namespace std;

int main()
{
	// 초기화
	set<int> s;

	// insert : 원소 삽입
	// 시간 복잡도 : O(log N)
	s.insert(10); // {10}
	s.insert(20); // {10,20}
	s.insert(10); // {10,20} // (중복된 값은 삽입되지 않음)

	// find : 원소 탐색
	// 시간 복잡도 : O(log N)
	auto it = s.find(10); // 10을 가리키는 반복자 반환
	if (it != s.end()) {
		cout << "Found : " << *it << endl; // 출력 Found : 10
	}
	else {
		cout << "Not Found" << endl;
	}

	// erase : 원소
	// 시간 복잡도 : O(log N)
	s.erase(10); // {20}

	// find 메서드를 사용하여 삭제된 원소를 찾으려 하면
	it = s.find(10);
	if (it != s.end()) {
		cout << "Found : " << *it << endl; 
	}
	else {
		cout << "Not Found" << endl; // 출력 Not Found
	}
}

set에서 원소 탐색

set에 특정 원소가 있는지 확인하려면 find() 메서드를 사용한다.
find()에서 원소를 찾으면 원소의 위치를 반환하고 , 없으면 end() 반복자를 입력한다.
시간복잡도는 O(log N)이다.
원소의 위치를 반환하기 때문에 원소 자체를 출력하고 싶다면 *연산자를 붙여 포인트로 출력해야 한다.

	// set을 사용해야 하는 경우
	// 1. 중복을 허용하지 않는 경우
	// ex. 유일한 사용자 ID를 저장하는 경우
	set<int> uniqueIds;
	uniqueIds.insert(1);
	uniqueIds.insert(2);
	uniqueIds.insert(1); // 중복 삽입 무시
	for (int id : uniqueIds) {
		cout << "User ID: " << id << endl;
	}

	// 2. 정렬된 순서가 필요한 경우
	// ex. 정렬된 데이터가 필요한 상황
	set<int> sortData;
	sortData.insert(5);
	sortData.insert(1);
	sortData.insert(3);
	for (int val : sortData) {
		cout << "Sorted Value : " << val << endl; // Sorted Value : 1,3,5
	}

	// 3. 탐색, 삽입, 삭제의 성능이 중요한 경우
	// ex. 데이터의 존재 여부를 빈번히 검사해야 하는 경우
	set<int> dataSet;
	dataSet.insert(15);
	if (dataSet.find(15) != dataSet.end()) {
		cout << "15 is in the set" << endl;
	}
#include <iostream>
#include <set>
#include <vector>
#include <unordered_set>

using namespace std;

	// set을 사용하지 말아야 하는 경우
	// 1. 중복된 원소를 허용해야 하는 경우
	// ex. 동일한 값을 여러 번 저장해야 하는 경우
	multiset<int> multiSet;
	multiSet.insert(10);
	multiSet.insert(10);
	cout << "Multiset contains " << multiSet.count(10) << "instance of 10" << endl;

	// 2. 정렬이 필요 없는 경우
	// ex. 순서가 증요하지 않고 단순히 데이터를 저장하고자 하는 경우
	vector<int> vec = { 5,3,8,1 };
	vec.push_back(10);
	cout << "Vector: ";
	for (int v : vec) {
		cout << v << " "; // 출력 Vector 5 3 8 1 10
	}
	cout << endl;

	// 3. O(1) 시간 복잡도가 필요한 경우
	// ex. 빈번한 삽입과 삭제가 필요한 경우
	unordered_set<int> unorderedSet;
	unorderedSet.insert(5);
	unorderedSet.insert(10);
	unorderedSet.insert(5);
	if (unorderedSet.find(10) != unorderedSet.end()) {
		cout << "10 is in the unordered set" << endl;
	}

정렬이 필요 없는 경우에는 set을 쓰지 않는다.


◆  STL(컨테이너-> map)

  ● key-value 쌍으로 이루어진 순서가 있는 집합
  ● 키는 중복을 허용하지 않음
  ● 원소가 자동으로 정렬된다. (균형 이진트리로 동작한다.)
  ● 삽입 / 삭제 / 탐색 : O(log N)
  ● 삽입 시 같은 키가 있으면 삽입되지 않고, 값을 업데이트한다.
  ● 삽입 / 삭제 / 탐색 시 자동 정렬되므로 정렬이 필요하지 않은 경우 비효율적이다.

키와 값의 쌍을 entry라고 하며 STL에서 std::pair 타입으로 표현한다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/map.cpp

 

codingtest_cpp/STL/map.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com



    map 컨테이너 설명 

    - map 은 키와 값의 쌍으로 이루어진 순서가 있는 집합입니다.
    - 키는 중복을 허용하지 않으며, 삽입되는 원소는 자동으로 정렬됩니다. (키를 기준으로 정렬한다.)
    - 내부적으로 균형 이진트리(일반적으로는 Red-Black Tree)로 구현되어 있습니다.
    -  삽입, 삭제, 탐색 등의 주요 연산은 O(log N)의 시간 복잡도를 가집니다.

    map을 사용해야 하는 경우 : 

    - 키와 값의 쌍을 효율적으로 저장하고 관리해야 할 때
    - 키를 기준으로 정렬된 순서로 데이터를 저장하고 싶을 때
    - 삽입, 삭제, 탐색 연산의 시간 복잡도가 O(log N)이면 충분히 빠를 경우

    map을 사용해야 하지 말아야 하는 경우:

    - 키의 중복을 허용해야 할 때 (이 경우 multimap을 사용)
    - 키의 순서가 중요하지 않은 경우 (이 경우 unordered_map 이 더 효율적일 수 있다.)
    - 데이터의 크기가 매우 크고, 삽입 및 탐색 연산이 더 빠른 시간복잡도를 요구하는 경우

코딩테스트에서는 multimap 쓰지 말고 다른 자료구조 쓰는 게 좋다.

#include <map>
#include <string>


using namespace std;

int main()
{
	// map 컨테이너 선언
	// key : int (학생 ID), value : string (학생 이름)
	map<int, string> studentMap;

	// 삽입 : 학생 ID와 이름을 맵에 추가
	// insert 함수
	// 인자 : 삽입할 키와 값 쌍 (key, value)
	// 동작 : 키가 존재하지 않으면 삽입, 존재하면 값을 업데이트
	studentMap.insert({ 101, "Alice" });
	studentMap.insert({ 102, "Bob" });
	studentMap.insert({ 103, "Charlie" });

	// 맵의 모든 요소 출력
	cout << "Initial map content :" << endl;
	for (const auto& pair : studentMap) {
		cout << "ID : " << pair.first << ", Name : " << pair.second << endl;
	}

	// 출력값 :
	// Initial map content :
	// ID : 101, Name : Alice
	// ID: 102, Name : Bob
	// ID : 103, Name : Charlie

	// 탐색 : 특정 ID로 학생 이름 찾기
	// find 함수
	// 인자 : 찾을 키 (key)
	// 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
	// 시간 복잡도 : O(log N)
	auto it = studentMap.find(102);
	if (it != studentMap.end()) {
		cout << "\nStudent with ID 102 found: " << it->second << endl;
	}
	else {
		cout << "\nStudent with ID 102 not found.\n";
	}

	// 출력값 : 
	// Student with ID 102 found: Bob

	// 업데이트 : 이미 존재하는 ID의 이름 변경
	// insert 함수
	// 인자 : 삽입할 키와 값 쌍(key, value)
	// 동작 : 키가 존재하지 않으면 삽입, 존재하면 값을 업데이트
	// 시간 복잡도 : O(log N)
	studentMap.insert({ 102, "Bobby" });
	cout << "\nAfter updating ID 102:\n";
	for (const auto& pair : studentMap) {
		cout << "ID: " << pair.first << ", Name: " << pair.second << endl;
	}
	// 출력값 : 
	// After updating ID 102:
	// ID: 101, Name : Alice
	// ID : 102, Name : Bob
	// ID : 103, Name : Charlie

	// 삭제 : 특정 ID의 학생 정보 삭제
	// erase 함수
	// 인자 : 삭제 할 키(key)
	// 동작 : 키가 존재하면 해당 키의 요소를 삭제
	// 시간 복잡도 : O(log N)
	studentMap.erase(101);
	cout << "\nAfter erasing ID 101:\n";
	for (const auto& pair : studentMap) {
		cout << "ID : " << pair.first << ", Name : " << pair.second << endl;
	}

	// 출력값 
	// After erasing ID 101:
	// ID: 102, Name : Bob
	// ID : 103, Name : Charlie

	return 0;
}

[] 연산자와 find 함수의 차이점

    [] 연산자 (배열 첨자 연산자)
    - 인자 : 접근할 키(key)
    - 동작 : 키가 존재하면 해당 키의 값을 반환, 없으면 키를 생성하고 기본값 출력
    - 시간 복잡도 : O(log N)

    find 함수
    - 인자 : 찾을 키(key)
    - 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
    - 시간 복잡도 : O(log N)

-> find 함수 사용을 추천하는 이유

    [] 연산자를 통해서 접근하게 되면은 존재하지 않는 키를 생성하고
    한 번이라도 접근하면 기본값이 설정되기 때문에  추후에 코드가 꼬일 수 있다.

   find 함수를 사용해서 키의 유무를 확인하고 있을 때만 접근하는 게 
   추후의 문제를 일으키지 않는 방법이 된다.

	// [] 연산자
	cout << "\nUsing [] operator:\n";
	cout << "Student with ID 103: " << studentMap[103] << endl; // 존재하는 키
	cout << "Student with ID 104: " << studentMap[104] << endl; // 존재하지 않는 키 , 기본값 설정

	// 출력값
	// Using [] operator:
	// Student with ID 103: Charlie
	// Student with ID 104 :

	// find 함수
	cout << "\nUsing find function:\n";
	it = studentMap.find(103);
	if (it != studentMap.end()) {
		cout << "Student with ID 103 found: " << it->second << endl;
	}
	else {
		cout << "Student with ID 103 not found.\n";
	}

	// 출력값
	// Using find function:
	// Student with ID 103 found: Charlie

◆  STL(컨테이너-> unordered_map)

  ●  key-value 쌍으로 이루어진 순서가 없는 집합
  ●  키는 중복을 허용하지 않음 
  ●  원소가 해시 테이블로 관리가 된다. (자동 정렬 되지 않는다)
  ●  삽입/삭제/탐색 : 평균적으로 O(1), 최악 O(N)
  ●  삽입 시 같은 키가 있으면 삽입하지 않고 값을 업데이트한다.
  ●  삽입/삭제/탐색 시 자동 정렬되지 않는다. 해시 함수에 따라 원소 순서가 결정된다.

map 같은 경우는 균형이진트리로 동작하기 때문에
자동으로 키를 통해 정렬을 하는건데
unordered_map은 해시 테이블로 되어 있기 때문에
자동으로 정렬 하지는 않다.
키를 접근하는건 평균적으로 O(1)이라서 빠르다.
보통의 경우는 O(N)까지 가진 않는다.
삽입/탐색/삭제를 빠르게 하고 싶을때 unordered_map 이 괜찮다.
대신 정렬은 안한다.
map을 해시테이블처럼 사용하는 게 아니라
unordered_map으로 해시테이블로 사용하는 게 좋다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/unordered_map.cpp

 

codingtest_cpp/STL/unordered_map.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com


    unordered_map  컨테이너 설명 

    - unordered_map 은 키와 값의 쌍으로 이루어진 순서가 있는 집합입니다.
    - 키는 중복을 허용하지 않으며, 삽입되는 원소는 해시 테이블로 관리됩니다.
    - 삽입, 삭제, 탐색 등의 주요 연산은 평균 O(1)의 시간 복잡도를 가집니다.
    - 최악의 경우, 해시 충돌로 인해 시간 복잡도가 O(N)이 될 수 있습니다.

    unordered_map 를 사용해야 하는 경우:

    - 키와 값의 쌍을 효율적으로 저장하고 관리해야 할 때
    - 키의 순서가 중요하지 않을 때
    - 평균 O(1)의 시간 복잡도를 갖는 빠른 삽입, 삭제, 탐색이 필요할 때

    unordered_map 를 사용하지 말아야 하는 경우:

    - 키의 순서가 중요할때 (이 경우 map을 사용)
    - 해시 함수가 비효율적으로 동작하여 충동이 많이 발생할 경우
    - 데이터의 크기가 매우 크고, 메모리 사용이 중요한 경우 (해시 테이블은 메모리 사용량이 많을 수 있다.)

#include <unordered_map>
#include <string>
using namespace std;

int main(){
	// unordered_map 컨테이너 선언
	// key : int (학생 ID), value : string (학생 이름)
	unordered_map<int, string> studentMap;

	// 삽입 : 학생 ID와 이름을 맵에 추가
	// insert 함수
	// 인자 : 삽입할 키와 값 쌍 (key, value)
	// 동작 : 키가 존재하지 않으면 삽입, 존재하면 값을 업데이트
	// 시간복잡도 : 평균 O(1), 최악 O(N)
	studentMap.insert({ 101, "Alice" });
	studentMap.insert({ 102, "Bob" });
	studentMap.insert({ 103, "Charlie" });

	// 맵의 모든 요소 출력
	cout << "Initial unordered_map content:\n";
	for (const auto& pair : studentMap) {
		cout << "ID: " << pair.first << ", Name: " << pair.second << endl;
	}

	// 출력값 :
	// Initial unordered_map content:
	// ID: 101, Name : Alice
	// ID : 102, Name : Bob
	// ID : 103, Name : Charlie

	// 탐색 : 특정 ID로 학생 이름 찾기
	// find 함수
	// 인자 : 찾을 키 (key)
	// 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
	// 시간 복잡도 : 평균 O(1), 최악 O(N)
	auto it = studentMap.find(102);
	if (it != studentMap.end()) {
		cout << "\nStudent with ID 102 found: " << it->second << endl;
	}
	else {
		cout << "\nStudent with ID 102 not found.\n";
	}

	// 출력값 :
	// Student with ID 102 found: Bob

	// 업데이트 : 이미 존재하는 ID의 이름 변경
	// insert 함수
	// 인자 : 삽입할 키와 값 쌍(key, value)
	// 동작 : 키가 존재하지 않으면 삽입, 존재하면 값을 업데이트
	// 시간 복잡도 : 평균 O(1), 최악 O(N)
	studentMap.insert({ 102, "Bobby" });
	cout << "\nAfter updating ID 102:\n";
	for (const auto& pair : studentMap) {
		cout << "ID: " << pair.first << ", Name: " << pair.second << endl;
	}

	// 출력값 :
	// After updating ID 102:
	// ID: 101, Name : Alice
	// ID : 102, Name : Bob
	// ID : 103, Name : Charlie

	// 삭제 : 특정 ID의 학생 정보 삭제
	// erase 함수
	// 인자 : 삭제 할 키(key)
	// 동작 : 키가 존재하면 해당 키의 요소를 삭제
	// 시간 복잡도 : 평균 O(1), 최악 O(N)
	studentMap.erase(101);
	cout << "\nAfter erasing ID 101:\n";
	for (const auto& pair : studentMap) {
		cout << "ID: " << pair.first << ", Name: " << pair.second << endl;
	}

	// 출력값 :
	// After erasing ID 101:
	// ID: 102, Name : Bob
	// ID : 103, Name : Charlie

	return 0;
}

[] 연산자와 find 함수의 차이점

    [] 연산자 (배열 첨자 연산자)
    - 인자 : 접근할 키(key)
    - 동작 : 키가 존재하면 해당 키의 값을 반환, 없으면 키를 생성하고 기본값 출력
    - 시간 복잡도 : 평균 O(1), 최악 O(N)

    find 함수
    - 인자 : 찾을 키(key)
    - 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
    - 시간 복잡도 : 평균 O(1), 최악 O(N)

	cout << "\nUsing [] operator:\n";
	cout << "Student with ID 103: " << studentMap[103] << endl; // 존재하는 키
	cout << "Student with ID 104: " << studentMap[104] << endl; // 존재하지 않는 키, 기본값 설정

	cout << "\nUsing find function:\n";
	it = studentMap.find(103);
	if (it != studentMap.end()) {
		cout << "Student with ID 103 found: " << it->second << endl;
	}
	else {
		cout << "Student with ID 103 not found.\n";
	}

	// 출력값 :
	// Using find function:
	// Student with ID 103 found: Charlie

정렬이 안되는거 말고는 map 과 문법적으로 동일하다.


◆  STL(컨테이너-> unordered_set)

  ●  중복을 허용하지 않는 순서가 없는 집합
  ●  원소가 해시 테이블로 관리가 된다. (자동 정렬 되지 않는다)
  ●  삽입/삭제/탐색 : 평균적으로 O(1), 최악 O(N)
  ●  중복을 허용하지 않는다.
  ●  삽입/삭제/탐색 시 자동 정렬되지 않는다. 해시 함수에 따라 원소 순서가 결정된다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/unordered_set.cpp

 

codingtest_cpp/STL/unordered_set.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

    unordered_set  컨테이너 설명 

    - unordered_set 은 중복을 허용하지 않는 순서가 없는 집합니다.
    - 원소는 해시 테이블로 관리되며, 자동으로 정렬되지 않습니다.
    - 삽입, 삭제, 탐색 등의 주요 연산은 평균 O(1)의 시간 복잡도를 가집니다.
    - 최악의 경우, 해시 충돌로 인해 시간 복잡도가 O(N)이 될 수 있습니다.

    unordered_set 를 사용해야 하는 경우:

    - 중복되지 않는 값의 집합을 효율적으로 저장하고 관리해야 할 때
    - 원소의 순서가 중요하지 않을 때
    - 평균 O(1)의 시간 복잡도를 갖는 빠른 삽입, 삭제, 탐색이 필요할 때

    unordered_map 를 사용하지 말아야 하는 경우:

    - 원소의 순서가 중요할때 (이 경우 set을 사용)
    - 해시 함수가 비효율적으로 동작하여 충동이 많이 발생할 경우
    - 데이터의 크기가 매우 크고, 메모리 사용이 중요한 경우 (해시 테이블은 메모리 사용량이 많을 수 있다.)

#include <iostream>
#include <unordered_set>

using namespace std;

int main() {
	// unordered_map 컨테이너 선언
	// key : int (학생 ID)
	unordered_set<int> studentMap;

	// 삽입 : 학생 ID와 이름을 맵에 추가
	// insert 함수
	// 인자 : 삽입할 값 (value)
	// 동작 : 키가 존재하지 않으면 삽입
	// 시간복잡도 : 평균 O(1), 최악 O(N)
	studentMap.insert(101);
	studentMap.insert(102);
	studentMap.insert(103);

	// 셋의 모든 요소 출력
	cout << "Initial unordered_set content:\n";
	for (const auto& value : studentMap) {
		cout << "ID: " << value << endl;
	}

	// 출력값 :
	// Initial unordered_map content:
	// ID: 101
	// ID : 102
	// ID : 103

	// 탐색 : 특정 ID로 학생 이름 찾기
	// find 함수
	// 인자 : 찾을 키 (key)
	// 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
	// 시간 복잡도 : 평균 O(1), 최악 O(N)
	auto it = studentMap.find(102);
	if (it != studentMap.end()) {
		cout << "\nStudent with ID 102 found.\n";
	}
	else {
		cout << "\nStudent with ID 102 not found.\n";
	}

	// 출력값 :
	// Student with ID 102 found

	// 삭제 : 특정 ID의 학생 정보 삭제
	// erase 함수
	// 인자 : 삭제할 값 (value)
	// 동작 : 값이 존재하면 해당 값을 삭제
	// 시간 복잡도 : 평균 O(1), 최악 O(N)
	studentMap.erase(101);
	cout << "\nAfter erasing ID 101:\n";
	for (const auto& value : studentMap) {
		cout << "ID: " << value << endl;
	}

	// 출력값 :
	// After erasing ID 101:
	// ID : 102
	// ID : 103
    
    	return 0;
}

[] 연산자는 unordered_set 에서는 사용할 수 없음. 대신  find 함수를 사용해야 한다.

    find 함수
    - 인자 : 찾을 값(value)
    - 동작 : 키가 존재하면 iterator 반환, 없으면 end() 반환
    - 시간 복잡도 : 평균 O(1), 최악 O(N)

	it = studentMap.find(103);
	if (it != studentMap.end()) {
		cout << "\nStudent with ID 103 found: " << *it << endl;
	}
	else {
		cout << "\nStudent with ID 103 not found.\n";
	}

	// 출력값 :
	// Student with ID 103 found: 103

◆  STL(컨테이너-> stack)

  ●  중복을 허용하는 순서가 있는 선형 데이터 구조
  ●  LIFO(Last In First Out), 최근에 들어온 원소가 먼저 삭제 된다.
  ●  삽입/삭제 :  O(1)
  ●  탐색 및 임의 접근 불가능하다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/stack.cpp

 

codingtest_cpp/STL/stack.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com


    각 메서드의 동작 및 시간 복잡도:
    - push : 스택의 맨 위에 원소를 추가합니다. 삽입된 원소는 기존 원소 위에 쌓이게 됩니다.
  예를 들어, push(3)를 호출하면 스택의 상태는 [1,2,3]이 됩니다. 시간 복잡도: O(1)
   - pop : 스택의 맨 이 원소를 제거합니다.  

#include <iostream>
#include <stack>
using namespace std;
int stack_func();
int main()
{
	stack_func();

	return 0;
}
int stack_func()
{
	stack<int> s;
	
	// 원소 삽입
	s.push(1); // push : 스택의 맨 위에 원소가 추가합니다. 스택의 상태는 [1] 입니다. 시간 복잡도 : O(1)
	s.push(2); // push : 스택의 맨 위에 원소가 추가합니다. 스택의 상태는 [1,2] 입니다. 시간 복잡도 : O(1)
	s.push(3); // push : 스택의 맨 위에 원소가 추가합니다. 스택의 상태는 [1,2,3] 입니다. 시간 복잡도 : O(1)

	// 맨 위 원소 확인
	cout << "Top element : " << s.top() << endl; // 출력 : 3
	// top() : 스택의 맨 위 원소를 반환합니다. 스택이 비어 있으면 예외를 던집니다. 예를 들어, 현재 스택의 상태는 [1,2,3] 이므로 top()은 3을 반환합니다. 시간 복잡도 : O(1)

	// 원소 삭제
	s.pop(); // pop(): 스택의 맨 위 원소를 제거합니다. 스택이 비어 있다면 예외를 던집니다. 현재 스택의 상태는 [1,2,3] 이므로 pop()은 3을 제거하여 [1,2]가 됩니다. 시간 복잡도 : O(1)
	cout << "Top element after pop : " << s.top() << endl; // 출력 : 2

	// 스택이 비어있는지 확인
	if (!s.empty())
	{
		cout << "Stack is not empty" << endl; // 출력 : Stack is not empty
	}
	// empty : 스택이 비어 있는지 여부를 확인합니다. 비어 있다면 true, 아니면 false를 반환합니다. 현재 스택의 상태는 [1,2] 이므로 empty()는 false를 반환합니다. 시간 복잡도 : O(1)

	// 스택의 크기 확인
	cout << "Stack size : " << s.size() << endl; // 출력 : 2

	// 스택에서 모든 원소를 pop 하여 출력
	while (!s.empty()) {
		cout << "Popping element : " << s.top() << endl;
		s.pop();
	}
	// 반복문을 통해 모든 원소를 제거합니다. 현재 스택 상태는 [1,2]이므로 pop()은 2와 1을 순서대로 제거합니다. 시간 복잡도 : O(1)

	// 스택이 비어있는지 확인
	if (s.empty()) {
		cout << "Stack is empty after popping all ememnts" << endl; //  출력: Stack is empty after popping all elements
	}
	// empty : 스택이 비어 있는지 여부를 확인합니다. 비어 있다면 true, 아니면 false를 반환합니다. 현재 스택의 상태는 빈 상태이므로 empty()는 true를 반환합니다. 시간 복잡도 : O(1)
	return 0;
}

◆  STL(컨테이너-> queue)

  ●  중복을 허용하는 순서가 있는 선형 데이터 구조
  ●  FIFO(First In First Out), 먼저 들어온 원소가 먼저 삭제 된다.
  ●  삽입/삭제 :  O(1)
  ●  탐색 및 임의 접근 불가능하다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/queue.cpp

 

codingtest_cpp/STL/queue.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com


    std:queue는 C++ STL에 포함된 컨테이너 어댑터 입니다.

    - FIFO(First-In-First-Out) 원칙에 따라 동작하는 데이터 구조입니다.
    - 좋은 사용 시기:
      - 데이터를 순서대로 처리해야 할 때, 예를 들면, 너비 우선 탐색(BFS) 알고리즘, 대기열 구현 등에 사용.
    - 성능 이슈 :
      - STL queue는 일반적으로 빠른 연산을 제공합니다. 하지만, 중간 요소에 직접 접근할 수 없습니다. 중간 요소를 검색하거나 수정하려면 다른 자료구조를 사용하는 것이 좋습니다.

#include <iostream>
#include <queue>

using namespace std;

int queue_func();
int main()
{
	queue_func();

	return 0;
}

int queue_func()
{
	queue<int> q;

	// push : 큐의 끝에 요소 추가, O(1)
	q.push(1);
	q.push(2);
	q.push(3);

	// front : 큐의 첫 번째 요소에 접근, O(1)
	cout << "Front element: " << q.front() << endl; // 출력: Front element: 1

	// pop : 큐가 첫 번째 요소 제거, O(1)
	q.pop();
	cout << "Front element after pop: " << q.front() << endl; // 출력: Front element after pop: 2

	// empty : 큐가 비어 있는지 확인, O(1)
	if (!q.empty())
	{
		cout << "Queue is not empty" << endl; // 출력: Queue is not empty
	}
	
	// size : 큐의 크기 확인, O(1)
	cout << "Queue size : " << q.size() << endl; // 출력: Queue size: 2

	// 성능 저하 예제:
	// queue는 중간 요소에 직접 접근할 수 없으므로, 중간 요소를 검색하거나 수정하는 연산이 필요한 경우
	// queue보다는 다른 자료 구조를 사용하는 것이 좋습니다.

	return 0;
}

◆  STL(알고리즘-> count)

  ●  데이터를 세는 함수
  ●  특정 값의 출현 횟수를 반환한다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/count.cpp

 

codingtest_cpp/STL/count.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int algorithm_func();
int main()
{
	algorithm_func();
	return 0;
}

int algorithm_func()
{
	// 예시 벡터 생성
	vector<int> v = { 1,2,3,4,5,1,2,1 };

	// count 함수 사용 예시
	// std::count 함수는 주어진 범위에서 특정 값이 몇 번 나타나는지 센다.
	// 여기서 v.begin()과 v.end()는 벡터의 시작과 끝을 나타내며,
	// 1은 찾고자 하는 값이다.
	int count_of_1 = count(v.begin(), v.end(), 1);

	// 결과 출력
	cout << "벡터에서 1의 갯수: " << count_of_1 << endl;
	
	// 출력값 :
	// 벡터에서 1의 갯수: 3
    
	return 0;
}

    시간 복잡도 :
    - std::count 함수의 시간 복잡도는 O(N)이다.
    - 여기서 N은 주어진 범위의 요소 수를 나타낸다.

    count 함수를 사용해야 하는 경우
    - 데이터 집합에서 특정 값의 출현 빈도를 알고 싶을 때 유용하다.
    -  예를 들어, 설문조사 결과에서 특정 답변이 몇 번 나왔는지 확인할 때 사용할 수 있다.


◆  STL(알고리즘-> sort)

  ●  데이터를 정렬하는 함수
  ●  정렬 기준을 전달하지 않으면 오름차순으로 동작한다.

  ●  사용자 정의형의 경우 무조건 정렬 기준을 전달 해야 한다.

int, double, char -> 오름차순으로 동작 할 수 있으나,

struct Axis
{
   int x,y;
}

-> 사용자 정의형인 경우에는 무조건 정렬 기준으로 동작해야 한다.
기본 타입에서도 오름차순이 아니라 사용자가 원하는 정렬 기준이 있다면
정렬 기준을 세번째 인자에 전달해야 한다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/sort.cpp

 

codingtest_cpp/STL/sort.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

#include <iostream>
#include <vector>
#include <algorithm> // sort() 함수가 포함된 헤더
using namespace std;

bool compare(int, int);
int sort_func();

int main()
{
	sort_func();
	return 0;
}

// 사용자 정의 비교 함수
bool compare(int a, int b)
{
	// 내림차순 정렬을 위한 비교 : a가 b보다 클 떄 true를 반환
	// sort 함수가 비교 시 compare(a,b)가 true를 반환하면 a가 b보다 앞에 있어야 한다고 판단하여
        // a와 b의 위치를 교환하지 않음
	return a > b;
}

int sort_func()
{
	// 정렬할 벡터 생성
	vector<int> v = { 5,2,9,1,5,6 };

	// 벡터를 오름차순으로 정렬
	// std::sort는 기본적으로 < 연산자를 사용하여 정렬
	// v.begin()은 첫 번쨰 요소, v.end()는 마지막 요소의 다음 위치를 가리킨다.
        // (v.end()는 정렬 대상에 포함되지 않는다.)
	sort(v.begin(), v.end());

	// 오름차순으로 정렬된 벡터를 출력
	cout << "오름차순 정렬: ";
	for (int n : v)
	{
		cout << n << ' ';
	}
	cout << endl;
	// 출력값 : 오름차순 정렬: 1 2 5 5 6 9

	// 벡터를 다시 섞어서 초기 상태로 되돌림
	v = { 5,2,9,1,5,6 };

	// 벡터를 사용자 정의 비교 함수로 정렬함 (내림차순)
	// std::sort는 compare 함수가 true를 반환할 때
        // 첫 번째 요소가 두 번째 요소보다 앞에 있어야 한다고 판단한다.
	// compare() 함수는 a > b 일때 true를 반환하므로,
        //큰 값이 작은 값에 오게 되어 내림차순 정렬이 이뤄진다.
	sort(v.begin(), v.end(), compare);

	// 내림차순으로 정렬된 벡터를 출력
	cout << "내림차순 정렬: ";
	for (int n : v) {
		cout << n << ' ';
	}
	cout << endl;
	// 출력값 : 내림차순 정렬: 9 6 5 5 2 1

	// 벡터를 다시 섞어서 초기 상태로 되돌린다.
	v = { 5,2,9,5,6 };

	// 벡터의 앞 3개 요소만 오름차순으로 정렬
	// v.begin()에서 v.begin() + 3 까지 정렬
	// 정렬 범위 : [5,2,9] (v.begin() + 3은 정렬 대상에 포함되지 않는다.)
	sort(v.begin(), v.begin() + 3);

	// 부분 정렬된 벡터 출력
	cout << "부분 정렬 ( 앞 3개 요소 오름차순 )" ;
	for (int n : v) {
		cout << n << ' ';
	}
	cout << endl;
	// 출력값 : 부분 정렬 ( 앞 3개 요소 오름차순 ) 2 5 9 5 6

	// 벡터를 다시 섞어서 초기 상태로 되돌린다
	v = { 5,2,9,1,5,6 }; 

	// 역방향 반복자를 사용해서 벡터를 내림차순으로 정렬
	// v.rbegin()은 마지막 요소, v.rend()는 첫 번째 요소의 이전 위치를 가리킨다.
        // (v.rend()는 정렬 대상에 포함되지 않는다.)
	sort(v.rbegin(), v.rend());

	// 역방향으로 정렬된 벡터 출력
	cout << "역방향 반복자로 정렬 (내림차순) : ";
	for (int n : v) {
		cout << n << ' ';
	}
	cout << endl;
	// 출력값 : 역방향 반복자로 정렬 (내림차순) : 9 6 5 5 2 1
	return 0;
}


◆  STL(알고리즘-> unique)

  ●  인접한 중복요소를 뒤로 재배치하는 함수
  ●  중복되지 않는 범위의 끝을 나타내는 반복자를 반환한다.

  ●  함수 사용 후 중복요소가 제거된 것이 아니다. 완전히 제거하려면 erase() 함수 추가 사용해야한다.

  ●  정렬된 상태에서 사용할 경우 모든 중복 요소를 제거할 수 있다.

 

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/unique.cpp

 

codingtest_cpp/STL/unique.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

    std::unique() 함수는 주어진 범위에서 인접한 중복 요소를 재배치 합니다.
    이 함수는 정렬된 상태에서 사용하면 모든 중복 요소를 올바르게 처리할 수 있습니다.

   주의해야 할 점:
   1. 정렬된 상태에서 사용 : unique 함수는 인접한 중복 요소만 재배치하므로,
      중복 요소를 완전히 제거하기 위해서는 범위가 정렬된 상태여야 합니다.
   2. 반환값 : 새로운 끝 부분을 반환합니다. 실제로 중복 요소를 제거하는 것이 아니라,
      중복되지 않은 요소를 앞으로 재배치합니다.
   3. 벡터 크기 조정 : 중복 요소를 재배치 한 후, 반환된 새로운 끝 부분까지 벡터를 잘라내어야 합니다.
   4. 새로운 범위 : 반환된 반복자는 중복되지 않은 요소로 이루어진 새로운 범위의 끝을 나타냅니다.
      이 범위는 벡터의 시작부터 반환된 반복자 전까지입니다.
   
시간 복잡도 :
    - std::unque 함수의 시간 복잡도는 O(N)입니다. 여기서 N은 범위 내의 요소 수 입니다.
    - std::vector::erase 함수의 시간 복잡도는 O(N)입니다.
      범위의 요소들을 제거하고나머지 요소들을 앞으로 이동시키기 때문입니다.

#include <iostream>
#include <vector>
#include <algorithm> // sort() 함수와 unique() 함수가 포함된 헤더
using namespace std;

int unique_func();

int main()
{
	unique_func();
	return 0;
}

int unique_func()
{
	// 예시 벡터 생성 (정렬되지 않은 상태)
	vector<int> v1 = { 3,1,2,3,2,4,1,5,3 };

	// 중복 요소 재배치 (정렬되지 않은 상태에서)
	auto last1 = unique(v1.begin(), v1.end());

	// 결과 출력  (정렬되지 않은 상태에서 unique 사용 후)
	cout << "정렬되지 않은 상태에서 unique 사용 후 :\n";
	for (auto it = v1.begin(); it != last1; ++it) {
		cout << *it << " ";
	}
	cout << endl;
	// 출력값
	// 정렬되지 않은 상태에서 unique 사용 후 :
	// 3 1 2 3 2 4 1 5 3

	
	// 벡터를 정렬된 상태로 만들기
	vector<int> v2 = { 3, 1, 2, 3, 2, 4, 1, 5, 3 };
	sort(v2.begin(), v2.end());

	// 중복 요소 재배치 (정렬된 상태에서)
	auto last2 = unique(v2.begin(), v2.end());

	// 새로운 끝 부분까지 벡터를 잘라내기
	v2.erase(last2, v2.end());

	// 결과 출력 (정렬된 상태에 unique 사용 후)
	cout << "정렬된 상태에서 unique 사용 후:\n";
	for (int num : v2) {
		cout << num << " ";
	}
	cout << endl;

	// 출력값
	// 정렬된 상태에서 unique 사용 후:
	// 1 2 3 4 5

	return 0;
}


◆  STL(알고리즘-> binary_search)

  ●  정렬된 범위에서 특정 값을 찾는 함수
  ●  값이 존재하면 true를, 존재하지 않으면 false를 반환한다.

  ●  이진 탐색을 사용하므로 O(logN)을 보장한다.

  ●  반드시 정렬된 상태에서 사용해야 한다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/binary_search.cpp

 

codingtest_cpp/STL/binary_search.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com


#include <iostream>
#include <vector>
#include <algorithm> // sort() 함수와 binary_search() 함수가 포함된 헤더
using namespace std;

int binary_search_func();

int main()
{
	binary_search_func();
	return 0;
}

int binary_search_func()
{
	// 예시 벡터 생성
	vector<int> v = { 1,3,4,5,7,9,10 };

	// 찾고자 하는 값
	int value1 = 5;
	int value2 = 6;
	
	// 이진 탐색 사용 예시
	bool found1 = binary_search(v.begin(), v.end(), value1);
	bool found2 = binary_search(v.begin(), v.end(), value2);

	// 결과 출력
	cout << "값 " << value1 << "를 찾는 중: " << (found1 ? "찾음" : "찾지 못함") << endl;
	cout << "값" << value2 << "를 찾는 중: " << (found2 ? "찾음" : "찾지 못함") << endl;
    // 출력값
    // 값 5를 찾는 중: 찾음
    // 값6를 찾는 중: 찾지 못함
	return 0;
}

   binary_search에 대한 설명
   std::binary_search 함수는 정렬된 범위에서 특정 값을 찾는 데 사용됩니다.
   이진 탐색 알고리즘을 사용하여 값의 존재 여부를 확인합니다.
   
   주의해야 할 점:
   1. 정렬된 상태에서 사용 : binary_search 함수는 정렬된 범위에서만 올바르게 동작합니다.
      정렬되지 않은 범위에서 사용하면 경과가 올바르지 않습니다.
   2. 반환값 : 찾고자 하는 값이 존재하면 true를 반환하고, 존재하지 않으면 false를 반환합니다.
   
   시간 복잡도 :
   std:binary_search 함수의 시간복잡도는 O(logN)입니다.
   여기서 N은 범위 내의 요소 수 입니다.
 
   예제:
   벡터가 {1,3,4,5,7,9,10}로 주어졌을 때,
   binary_search(v.begin(), v.end(), 5)를 호출하면 true를 반환하고,
   binary_search(v.begin(), v.end(), 6)를 호출하면 false를 반환합니다.


◆  STL(알고리즘-> max_element / min_element)

  ●  범위내에서 가장 큰(또는 작은) 원소를 찾는 함수
  ●  반복자 범위에서 가장 큰(또는 작은) 원소릴 가리키는 반복자를 반환한다.

  ●  선형 탐색을 하므로 시간 복잡도는 O(N)이다.

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/max_element.cpp

 

codingtest_cpp/STL/max_element.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com

https://github.com/dremdeveloper/codingtest_cpp/blob/main/STL/min_element.cpp

 

codingtest_cpp/STL/min_element.cpp at main · dremdeveloper/codingtest_cpp

Contribute to dremdeveloper/codingtest_cpp development by creating an account on GitHub.

github.com


   max_element 대한 설명
   std::max_element 함수는 주어진 범위에서 가장 큰 요소를 찾는 데 사용됩니다.
   이 함수는 선형 탐색 알고리즘을 사용하여 값을 찾습니다.
   
   설명 :
   1. 반환값 : 가장 큰 값을 가리키는 반복자를 반환합니다.
      반환된 반복자를 사용하여 해당 값에 접근 할 수 있습니다.
   2. 범위 : 시작 반복자와 끝 반복자를 인자로 받아, 이 범위 내에서 탐색을 수행합니다.

   시간 복잡도 :
   std:max_element 함수의 시간복잡도는 O(N)입니다.
   여기서 N은 범위 내의 요소 수 입니다.
   
   예제:
   벡터가 {1,3,4,5,7,9,10}로 주어졌을 때,
   max_element(v.begin(), v.end())를 호출하면 반복자가 10을 가리킵니다.


   min_element 대한 설명
   std::min_element 함수는 주어진 범위에서 가장 작은 요소를 찾는 데 사용됩니다.
   이 함수는 선형 탐색 알고리즘을 사용하여 값을 찾습니다.
   
   설명 :
   1. 반환값 : 가장 작은 값을 가리키는 반복자를 반환합니다.
      반환된 반복자를 사용하여 해당 값에 접근 할 수 있습니다.
   2. 범위 : 시작 반복자와 끝 반복자를 인자로 받아, 이 범위 내에서 탐색을 수행합니다.
   
   시간 복잡도 :
   std:min_element 함수의 시간복잡도는 O(N)입니다.
   여기서 N은 범위 내의 요소 수 입니다.
 
   예제:
   벡터가 {1,3,4,5,7,9,10}로 주어졌을 때,
   min_element(v.begin(), v.end())를 호출하면 반복자가 1을 가리킵니다.


#include <iostream>
#include <vector>
#include <algorithm> //  max_element() 함수와 min_element() 함수가 포함된 헤더
using namespace std;

int max_element_func();
int min_element_func();

int main()
{
	max_element_func();
	min_element_func();
	return 0;
}

int max_element_func()
{
	// 예시 벡터 생성
	vector<int> v = { 1,3,4,5,7,9,10 };

	// 최대값 찾기
	auto max_it = max_element(v.begin(), v.end());

	// 결과 출력
	cout << "최댓값 : " << *max_it << " (위치 : " << distance(v.begin(), max_it) << ")" << endl;
	
	// 출력값
	// 최댓값 : 10 (위치 : 6)
	return 0;
}


int min_element_func()
{
	// 예시 벡터 생성
	vector<int> v = { 1,3,4,5,7,9,10 };

	// 최소값 찾기
	auto min_it = min_element(v.begin(), v.end());

	// 결과 출력
	cout << "최솟값 : " << *min_it << " (위치 : " << distance(v.begin(), min_it) << ")" << endl;

	// 출력값
	// 최솟값: 1 (위치 : 0)
	return 0;
}

◆  STL(알고리즘-> next_permutation)

  ●  주어진 범위의 요소들에 대해 다음 순열을 생성한다.
  ●  순열이 더 이상 없으면  false, 그렇지 않으면 true 이다.

  ●  모든 순열을 생성하기 위해서는 정렬되어 있어야 한다.

  ●  시간 복잡도는 O(N*N!)

   next_permutation 대한 설명
   std::next_permutation 함수는 주어진 범위에서 사전식 순서로 다음 순열을 생성합니다.
   이 함수는 내부적으로 요소의 순서를 비교하여 다음 순열을 만듭니다.
   
   주의해야 할 점 :
   1. 정렬된 상태에서 사용 : next_permutation 함수는 범위가 정렬된 상태에서 시작 할 때 모든 순열을
      올바르게 생성할 수 있습니다. 정렬되지 않은 상태에서 시작하면 모든 순열을 생성하지 못합니다.
   2. 반환값 : 다음 순열이 존재하면 true를 반환하고, 더 이상 다음 순열이 없으면 false를 반환하여
      주어진 범위를 처음 순열(가장 작은 순열)로 재설정합니다.
   3. 시간 복잡도 : 함수 자체의 시간 복잡도는 O(N)입니다. 모든 순열을 생성하기 위해서 반복적으로
      호출하면 총 시간 복잡도는 O(N*N!)이 됩니다. 여기서 N은 범위 내의 요소 수 입니다.
   
   
   next_permutation 사용 예제:
   벡터가 {1,2,3}로 주어졌을 때,
   next_permutation(v.begin(), v.end())를 호출하면,
   벡터는 {1,3,2}로 변환됩니다.
   
   벡터가 {3,2,1}로 주어졌을 때,
   next_permutation(v.begin(), v.end())를 호출하면,
   벡터는 다시 {1,2,3}로 재설정됩니다.

#include <iostream>
#include <vector>
#include <algorithm> //  next_permutation() 함수가 포함된 헤더
using namespace std;

int next_permutation_func();

int main()
{
	next_permutation_func();
	return 0;
}

void print_permutaions(vector<int> v)
{
	do {
		// 현재 순열 출력
		for (int num : v) {
			cout << num << " ";
		}
		cout << endl;
	} while (next_permutation(v.begin(), v.end()));
}

int next_permutation_func()
{
	// 예시 벡터 생성
	vector<int> v = { 3,2,1 };
	
	// 정렬되지 않은 경우
	cout << "정렬되지 않은 경우:\n";
	vector<int> v_unsorted = v;
	print_permutaions(v_unsorted);

	// 벡터를 정렬된 상태로 만들기
	sort(v.begin(), v.end());

	// 정렬된 경우
	cout << "\n정렬된 경우:\n";
	vector<int> v_sorted = v;
	print_permutaions(v_sorted);

	// 출력값
	// 정렬되지 않은 경우:
	// 3 2 1
	// 정렬된 경우 :
	// 1 2 3
	// 1 3 2
	// 2 1 3
	// 2 3 1
	// 3 1 2
	// 3 2 1

	return 0;
}

C++ 문법 중에 몰랐던 것들이 꽤나 있어서 놀랬다.

일주일 단위로  복습해야겠다.

'C++ > C++ : 코팅테스트 합격자 되기(인프런)' 카테고리의 다른 글

스택 문제 풀이 09번~10번 - 코딩 테스트 합격자 되기 C++  (1) 2024.10.10
7장 큐  (1) 2024.10.10
6장 스택  (3) 2024.10.07
3장 시간 복잡도  (1) 2024.09.27