-
% 연산을 이용한 Circular Array 구현 및 응용Data Structure 2021. 1. 30. 22:36
PS를 하면 종종 Circular Array의 필요성을 느낄 때가 있다
아래의 문제들은 내가 circular 배열을 이용해서 푼 문제들의 목록이다
그렇다면 circular array는 뭐고, problem solving에 어떻게 활용될까?
Circular Array는, 기존의 배열의 개념에 "마지막 element 다음 원소는 첫 번째 element"라는 조건을 추가한 자료구조다
크기가 N인 Array가 있다고 가정할 때, 일반적으로는 (index는 0~n-1) n-1번째 element가 마지막이고, n번째 부터는 접근이 불가능하다. 그러므로 인덱스를 배열의 크기로 제한해야 할 필요가 있다.
하지만 circular array에서는 이러한 제한이 없어진다. n-1번째 element의 다음(n번째)은 0번째 element이고, 그 다음(n+1번째)는 1번째 element이고, 등등
이러한 circular array의 구현은 생각보다 아주 간단하게 할 수 있다.
기존에 index를 통해 array에 접근했다면, 거기에 % 연산자를 추가해 (index % n)번째 array에 접근하면 된다.
위의 그림처럼 현재 current가 4라고 했을 때 current에서 3만큼 이동한 배열 값은 (4 + 3)%6 = 1이 된다.
그림에서도 3칸 이동한 index가 1임을 나타낸다.
이러한 circular array의 개념은 배열의 회전 등에서 응용할 수 있다.
예를 들어, [0,1,2,3,4,5]의 배열을 2번 left shift 한다고 가정했을 때 결과는 [2,3,4,5,0,1]이 된다.
이렇게 left shift된 배열에 접근하는 방법에는, 실제로 배열을 한칸씩 밀어서 구현할 수도 있겠지만 circular array의 개념을 적용하여 접근하는 index에 변화를 주어도 가능하다.
int n = 6; int array[6] = { 0,1,2,3,4,5 }; int newArray[6]; for (int i = 0; i < n - 2; i++) { newArray[i] = array[i + 2]; } for (int i = 0; i < 2; i++) { newArray[n - 2 + i] = array[i]; }//shift된 만큼 따로 옮겨주어야 함 for (int i = 0; i < n; i++) { newArray[i] = array[(i + 2) % n]; }
직접 array를 옮겨주게 된다면 index가 n을 넘어갈 수 없기 때문에 위의 두 개의 for문처럼 두 번에 나누어서 옮겨줘야 한다.
하지만 circular array의 개념을 적용한다면 index는 2,3,4,5,0,1로 순차적으로 접근하기 때문에 한 번의 연산만으로도 가능하다.
그런데, 위와 같이 index가 증가하는 방향인 left shift에서는 적용이 가능하지만, index를 감소시킨 right shift에서는 적용이 힘들 수 있다.
Array[ (i -1) % n]을 하게 되면, i가 0일 때 필연적으로 에러가 발생하기 때문이다.
그러한 때에는 index가 0 이상이 될 만큼 n을 더해주면 된다. 어떤 수에 n의 배수를 더한 뒤 n으로 나눈 나머지는 처음 수와 항상 같기 때문이다.
이러한 개념을 응용해서 left, right shift된 배열에 대해서도 접근할 수 있다.
중요한 것은 index를 이용한 circular array의 구현이 실제 데이터에 적용되지 않아도 된다는 점 이다 ... !
삼성 20년도 하반기 기출문제인 '컨베이어 벨트 위의 로봇'에서는 1초가 지날 때 마다 내구도를 지닌 컨베이어 벨트(배열)을 1칸씩 이동시켜야 한다.
그러한 이동 과정을 실제로 구현한다면 아마 두 개의 큐를 이용해서, 두 번의 pop과 push가 발생할 것이다.
하지만 컨베이어 벨트가 이동한다는 행위를, 배열을 직접 값을 변경해서 구현하지 않고 circular index를 통해 추상적으로 구현한다면 아래와 같이 접근할 수 있다.
#include <cstdio> int arr[2][205], n, k,ans,idx; int count() { int sum = 0; for (int i = 0; i < 2 * n; i++) if (arr[1][i] <= 0)sum++; return sum; } int main() { //arr[0] = 로봇 유무, arr[1] = 내구도 scanf("%d%d", &n, &k); for (int i = 0; i < 2 * n; i++)scanf("%d", &arr[1][i]); while (++ans) { arr[0][(idx + n - 1) % (2 * n)] = 0; idx = (idx - 1 + 2 * n) % (2 * n); //index를 -1 for (int i = n-1; i >= 0; i--) { int cur = (idx + i) % (2 * n); //현재 index if (i == n - 1) { arr[0][cur] = 0; continue; } if (!arr[0][cur])continue; int next = (cur + 1) % (2 * n); //현재 index 다음의 index if (!arr[0][next] && arr[1][next] >= 1) { arr[0][next] = 1; arr[0][cur] = 0; arr[1][next]--; } } if (arr[0][idx] == 0 && arr[1][idx] >= 1) { arr[0][idx] = 1; arr[1][idx]--; } if (count() >= k)break; } printf("%d", ans); return 0; }
컨베이어 벨트의 길이는 주어진 n의 두 배인 2*n이다.
현재 index는 계속해서 감소하게 구현하고(index가 0이 된다면 2*n), 현재 index에서 n만큼 오른쪽으로 이동한 칸에서 부터 감소하는 방향으로 로봇의 이동을 시작한다.
이렇게 구현한다면 하나의 array만 가지고 문제를 해결할 수 있다.
circular index를 이용한다면 배열의 잘못된 index 접근이 사라진다.
circular index의 개념은 "배열이 회전하는 문제"에 대해 적용할 수 있다.
원판 돌리기, 톱니바퀴 문제 또한 원형의 배열이 회전하는 성질을 가지고 있기 때문에 circular index를 이용해서 해결할 수 있다.
'Data Structure' 카테고리의 다른 글
Computer Science에서의 Tree란 (0) 2021.02.20 Array와 Linked List의 차이점 (0) 2021.01.31 Full, Perfect, Complete ? 이진트리의 형태 (0) 2021.01.12 ADT Stack의 정의와 연산 구현 (create, push, pop, top, empty) (0) 2021.01.11 모든 정점을 최소 비용으로 연결하는 MST - Minimum Spanning Tre (0) 2020.12.17