2022. 1. 21. 16:55ㆍAlgorithm/1강
1. 정의와 성질
배열 - 메모리 상에 원소를 연속하게 배치한 자료구조
배열의 성질
1. O(1)에 k번째 원소를 확인/변경 가능
2. 추가적으로 소모되는 메모리의 양(=overhead)가 거의 없음
3. Cache hit rate가 높음
4. 메모리 상에 연속한 구간을 잡아야 해서 할당에 제약이 걸림
2. 기능과 구현
기능
1. 임의의 위치에 있는 원소를 확인/변경 = O(1)
2. 원소를 끝에 추가 = O(1)
3. 마지막 원소를 제거 = O(1)
4. 임의의 위치에 원소 추가/제거 = O(N)
구현 - insert, erase 함수
수정 전 코드
#include <bits/stdc++.h>
using namespace std;
void insert(int idx, int num, int arr[], int &len)
{
len++;
int tmp[len];
for (int i = 0; i < idx; i++)
{
tmp[i] = arr[i];
}
tmp[idx] = num;
for (int i = idx + 1; i < len; i++)
{
tmp[i] = arr[i - 1];
}
for (int i = 0; i < len; i++)
{
arr[i] = tmp[i];
}
}
void erase(int idx, int arr[], int &len)
{
len--;
int tmp[len];
for (int i = 0; i < idx; i++)
{
tmp[i] = arr[i];
}
for (int i = idx; i < len; i++)
{
tmp[i] = arr[i + 1];
}
for (int i = 0; i < len; i++)
{
arr[i] = tmp[i];
}
}
void printArr(int arr[], int &len)
{
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << "\n\n";
}
void insert_test()
{
cout << "***** insert_test *****\n";
int arr[10] = {10, 20, 30};
int len = 3;
insert(3, 40, arr, len); // 10 20 30 40
printArr(arr, len);
insert(1, 50, arr, len); // 10 50 20 30 40
printArr(arr, len);
insert(0, 15, arr, len); // 15 10 50 20 30 40
printArr(arr, len);
}
void erase_test()
{
cout << "***** erase_test *****\n";
int arr[10] = {10, 50, 40, 30, 70, 20};
int len = 6;
erase(4, arr, len); // 10 50 40 30 20
printArr(arr, len);
erase(1, arr, len); // 10 40 30 20
printArr(arr, len);
erase(3, arr, len); // 10 40 30
printArr(arr, len);
}
int main(void)
{
insert_test();
erase_test();
}
수정 후 코드
#include <bits/stdc++.h>
using namespace std;
void insert(int idx, int num, int arr[], int &len)
{
len++;
for (int i = len - 1; i > idx; i--)
{
arr[i] = arr[i - 1];
}
arr[idx] = num;
}
void erase(int idx, int arr[], int &len)
{
len--;
for (int i = idx; i < len; i++)
{
arr[i] = arr[i + 1];
}
}
void printArr(int arr[], int &len)
{
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << "\n\n";
}
void insert_test()
{
cout << "***** insert_test *****\n";
int arr[10] = {10, 20, 30};
int len = 3;
insert(3, 40, arr, len); // 10 20 30 40
printArr(arr, len);
insert(1, 50, arr, len); // 10 50 20 30 40
printArr(arr, len);
insert(0, 15, arr, len); // 15 10 50 20 30 40
printArr(arr, len);
}
void erase_test()
{
cout << "***** erase_test *****\n";
int arr[10] = {10, 50, 40, 30, 70, 20};
int len = 6;
erase(4, arr, len); // 10 50 40 30 20
printArr(arr, len);
erase(1, arr, len); // 10 40 30 20
printArr(arr, len);
erase(3, arr, len); // 10 40 30
printArr(arr, len);
}
int main(void)
{
insert_test();
erase_test();
}
전체를 특정 값으로 초기화시키는 3가지 방법
1. cstring 헤더에 있는 memset 함수 활용
0이나 -1이 아닌 다른 값을 넣으면 오동작하고 2차원 이상의 배열을 함수의 인자로 넘겨 memset을 하면 잘못 들어갈 때가 있다.
int a[21];
int b[21][21];
memset(a, 0, sizeof a);
memset(b, 0, sizeof b);
2. for문을 돌면서 값을 일일이 다 바꾸는 방식
코드가 투박해도 실수할 여지가 없다.
int a[21];
int b[21][21];
for (int i = 0; i < 21; i++){
a[i] = 0;
}
for (int i = 0; i < 21; i++){
for (int j = 0; j < 21; j++){
b[i][j] = 0;
}
}
3. alorithm 헤더의 fill 함수 이용
실수할 여지도 없고 코드도 짧다.
int a[21];
int b[21][21];
fill(a, a+21, 0);
for (int i = 0; i < 21; i++){
fill(b[i], b[i]+21, 0);
}
3. STL vector
#include <bits/stdc++.h>
using namespace std;
int main(void) {
vector<int> v1(3, 5); // {5,5,5};
cout << v1.size() << '\n'; // 3
v1.push_back(7); // {5,5,5,7};
vector<int> v2(2); // {0,0};
v2.insert(v2.begin()+1, 3); // {0,3,0};
vector<int> v3 = {1,2,3,4}; // {1,2,3,4}
v3.erase(v3.begin()+2); // {1,2,4};
vector<int> v4; // {}
v4 = v3; // {1,2,4}
cout << v4[0] << v4[1] << v4[2] << '\n';
v4.pop_back(); // {1,2}
v4.clear(); // {}
}
insert, erase - O(N)
push_back, pop_back - O(1)
vector에서 =를 사용하면 deep copy가 발생한다. deep copy는 그 내용을 가진 객체를 따로 또 만들어 서로 영향을 주지 않는 copy이고 shallow copy는 같은 객체를 참조하기 때문에 값을 바꾸면 서로 영향을 준다.
vector에 있는 원소 순회 방법
1. range-based for loop
vector<int> v1 = {1,2,3,4,5,6};
for(int e : v1){
cout << e << ' ';
}
위의 코드처럼 range-based for loop를 돌리면 복사된 값이 e에 들어가고 아래 코드처럼 참조자를 붙여 돌리면 원본이 e에 들어가게 된다.
vector<int> v1 = {1,2,3,4,5,6};
for(int& e : v1){
cout << e << ' ';
}
이 기능은 vector 뿐 아니라 list, map, set 등에서도 모두 사용할 수 있어 유용하지만 C++11부터 추가된 기능이기 때문에 코딩테스트가 C++11을 지원하지 않으면 사용할 수 없다.
2. for문
vector<int> v1 = {1,2,3,4,5,6};
for(int i = 0; i < v1.size(); i++){
cout << v1[i] << ' ';
}
위의 코드처럼 무난하게 for문으로 인덱스를 하나씩 다 돌아도 된다. 하지만 아래 코드처럼 쓰면 잘못될 수 있다. 기본적으로 vector의 size 메소드는 시스템에 따라 unsigned int 혹은 unsigned long long을 반환한다. 그렇기 때문에 32비트 컴퓨터 기준 아래의 코드 같이 쓰면 v1이 빈 vector일 때 v1.size() - 1이 unsigned int 0에서 int 1을 빼는 식이 되고, unsigned int와 int를 연산하면 unsigned int로 자동 형변환이 발생하기 때문에 (unsigned int)0 - (int)1은 -1이 아니라 4294967295이 되어버리는 불상사가 발생한다. 4294967295이라는 이상한 값은 unsigned int overflow로 인해 생기게 된 값이다. 결국 런타임 에러를 마주하게 되니까 주의해서 사용해야 한다.
vector<int> v1 = {1,2,3,4,5,6};
for(int i = 0; i < v1.size() - 1; i++){
cout << v1[i] << ' ';
}
아래는 vector에 대한 레퍼런스 사이트.
http://www.cplusplus.com/reference/vector/vector/
vector - C++ Reference
difference_typea signed integral type, identical to: iterator_traits ::difference_type usually the same as ptrdiff_t
www.cplusplus.com
4. 연습 문제
10808번: 알파벳 개수
단어에 포함되어 있는 a의 개수, b의 개수, …, z의 개수를 공백으로 구분해서 출력한다.
www.acmicpc.net
아래 코드는 투박하고 단순한 코드지만 통과한 코드이다.
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int alpha[26] = {
0,
};
string s;
cin >> s;
for (char c : s)
{
switch (c)
{
case 'a':
alpha[0]++;
break;
case 'b':
alpha[1]++;
break;
case 'c':
alpha[2]++;
break;
case 'd':
alpha[3]++;
break;
case 'e':
alpha[4]++;
break;
case 'f':
alpha[5]++;
break;
case 'g':
alpha[6]++;
break;
case 'h':
alpha[7]++;
break;
case 'i':
alpha[8]++;
break;
case 'j':
alpha[9]++;
break;
case 'k':
alpha[10]++;
break;
case 'l':
alpha[11]++;
break;
case 'm':
alpha[12]++;
break;
case 'n':
alpha[13]++;
break;
case 'o':
alpha[14]++;
break;
case 'p':
alpha[15]++;
break;
case 'q':
alpha[16]++;
break;
case 'r':
alpha[17]++;
break;
case 's':
alpha[18]++;
break;
case 't':
alpha[19]++;
break;
case 'u':
alpha[20]++;
break;
case 'v':
alpha[21]++;
break;
case 'w':
alpha[22]++;
break;
case 'x':
alpha[23]++;
break;
case 'y':
alpha[24]++;
break;
case 'z':
alpha[25]++;
break;
default:
break;
}
}
for (int i = 0; i < 26; i++)
{
cout << alpha[i] << " ";
}
return 0;
}
위의 코드를 더 정리하면 아래 코드와 같다. 아스키코드를 이용해서 switch 문의 긴 코드를 freq[c-'a']++; 한 줄로 바꿀 수 있다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
int freq[26] = {0,};
string s;
cin >> s;
for (auto c:s){
freq[c-'a']++;
}
for (int i = 0; i < 26; i++){
cout << freq[i] << " ";
}
return 0;
}
0X01강의 연습문제 문제 2
#include <bits/stdc++.h>
using namespace std;
int func2(int arr[], int N)
{
int freq[101] = {0,};
for (int i = 0;i < N; i++){
if (freq[100 - arr[i]]){
return (1);
}
freq[arr[i]]++;
}
return (0);
}
이전의 0X01강에서는 O(N^2)의 코드를 짰었지만 이번에는 O(N)의 시간복잡도를 갖는 코드를 짜봤다.
참고자료
[실전 알고리즘] 0x03강 - 배열
[실전 알고리즘] 0x03강 - 배열
안녕하세요, 바킹독입니다.. 저번 단원의 내용인 코드 작성 요령 II는 순한 맛이었는데 오늘건 그냥 단맛입니다. 난이도가 굉장히 낮으니 긴장 푸시고 강의로 들어가겠습니다. 목차는 따로 설명
blog.encrypted.gg
'Algorithm > 1강' 카테고리의 다른 글
[실전 알고리즘] 0x04강 - 연결 리스트 (0) | 2022.01.21 |
---|---|
[실전 알고리즘] 0x02강 - 기초 코드 작성 요령 II (0) | 2022.01.21 |
[실전 알고리즘] 0x01강 - 기초 코드 작성 요령 I (0) | 2022.01.20 |