Storing Elements in a Row

개념 확인: Arrays


연속 저장의 주소 계산과 위치에 따라 달라지는 원소 이동 비용을 계산한다.

문제 1

인덱스로 주소 계산하기

한 칸이 8바이트인 배열의 첫 칸 주소가 3000이다. 주소는 addr(i) = base + i × size로 계산한다.

  1. 인덱스 7인 칸의 주소를 구한다.
  2. 주소가 3096인 칸의 인덱스를 구하고, 이 칸이 1부터 세면 몇 번째 칸인지 적는다.
  3. 원소가 1,000개일 때와 1,000,000개일 때 인덱스로 주소를 구하는 계산량이 달라지는지 적는다.
  4. Python list에 정수, 문자열, 목록을 섞어 담아도 이 주소 계산이 성립하는 이유를 적는다.
해설 펼치기
  1. 3000 + 7 × 8 = 3056이다.
  2. (3096 − 3000)/8 = 12이므로 인덱스 12이고, 1부터 세면 13번째 칸이다. 인덱스는 첫 칸에서 떨어진 칸 수다.
  3. 달라지지 않는다. 원소 수와 관계없이 곱셈 한 번과 덧셈 한 번으로 주소를 구한다.
  4. Python list의 칸에는 객체 자체가 아니라 같은 크기의 참조가 들어간다. 칸의 크기가 같으므로 주소 계산이 성립한다.
문제 2

허용되는 인덱스

a = ["p", "y", "t", "h", "o", "n"]
  1. a[5], a[-2], a[6], a[-7]의 결과를 각각 적는다.
  2. 같은 원소를 담은 List 인터페이스의 get(i)에서 허용되는 i의 범위를 적는다.
  3. get(-2)가 Python list의 a[-2]와 다르게 처리되는 이유를 적는다.
해설 펼치기
  1. a[5]는 "n", a[-2]는 끝에서 두 번째인 "o", a[6]과 a[-7]은 IndexError다.
  2. n = 6이므로 0 ≤ i < 6이다.
  3. List 인터페이스는 0 ≤ i < n만 유효한 위치로 정하고 음수 인덱스를 허용하지 않는다. 끝에서부터 세는 음수 인덱스는 Python list가 따로 제공하는 기능이다.
문제 3

원소 수와 확보한 칸 수

ArrayStack의 내부 배열 a가 [d, o, g, s, -, -]이고 원소 수가 n = 4다. -는 비어 있는 칸이다.

  1. a.length를 적는다.
  2. get(3)의 반환값을 적는다.
  3. set(1, t)의 반환값과 호출 뒤 내부 배열을 적는다.
  4. 내부 배열에 칸이 6개인데도 get(4)를 허용하지 않는 이유를 적는다.
해설 펼치기
  1. a.length는 확보한 칸 수 6이다.
  2. s다. 인덱스 검사를 통과한 뒤 a[3] 한 칸만 읽는다.
  3. 기존 값 o를 반환하고 내부 배열은 [d, t, g, s, -, -]가 된다. a.length와 n은 바뀌지 않는다.
  4. 유효한 위치는 확보한 칸 수가 아니라 원소 수 n = 4로 정한다. a[4]는 아직 원소가 없는 빈 칸이다. 내부 배열은 원소 수보다 칸을 더 확보해 둘 수 있으므로 a.length와 n은 다른 값이다.
문제 4

연산 열의 원소 이동 수

원소 6개가 담긴 ArrayStack에 다음 연산을 차례로 실행한다. 용량은 충분해 내부 배열의 칸 수(a.length)는 바뀌지 않는다.

add(2, x) → add(7, y) → remove(3) → remove(6)
  1. 각 호출 직전의 n과 옮긴 원소 수를 적는다.
  2. 네 호출에서 옮긴 원소 수의 합을 구한다.
  3. 3과 4는 원소 네 개가 든 [k, l, m, p, -, -]에서 각각 따로 시작한다. add(2, x)를 하며 a[3] = a[2]부터 실행하면 한 번 뒤 배열과 잃는 원소는 무엇인가? 올바른 복사 방향도 적는다.
  4. 3의 원래 배열에서 remove(1)을 하며 a[2] = a[3]부터 실행하면 한 번 뒤 배열과 잃는 원소는 무엇인가? 올바른 복사 방향도 적는다.
해설 펼치기
  1. add(2, x): n = 6, n − i = 4개. add(7, y): n = 7, i = n이라 0개. remove(3): n = 8, n − i − 1 = 4개. remove(6): n = 7, n − i − 1 = 0개.
  2. 4 + 0 + 4 + 0 = 8개다. 마지막 원소 수는 6이다.
  3. a[3] = a[2] 뒤에는 [k, l, m, m, -, -]가 되어 아직 옮기지 않은 p를 잃는다. 삽입할 때는 오른쪽 끝부터 한 칸씩 밀어야 한다.
  4. a[2] = a[3] 뒤에는 [k, l, p, p, -, -]가 되어 아직 옮기지 않은 m을 잃는다. 삭제할 때는 지운 자리 바로 뒤, 곧 왼쪽부터 당겨야 한다.
문제 5

배열이 유리한 일과 불리한 일

  1. 다음 요구마다 자주 부르는 연산과 배열에서의 비용을 적고 배열이 적합한지 판단한다. (가) 학번 순서로 저장한 점수를 번호로 조회한다. (나) 센서 측정값을 받은 순서대로 끝에 계속 붙인다. (다) 새 요청을 대기 목록의 맨 앞에 자주 끼워 넣는다.
  2. add(i, x)의 최선·평균·최악의 경우가 어떤 삽입 위치이고 비용이 얼마인지 적는다.
  3. 원소 30,000개를 한 칸씩 미는 데 반복문은 1.20 ms, 슬라이스 대입은 0.25 ms가 걸렸다. 두 방법이 모두 Θ(n)인데 시간이 약 4.8배 다른 이유를 적는다.
해설 펼치기
  1. (가) get(i)로 Θ(1)이라 적합하다. (나) add(n, x)로 옮길 원소가 없어 적합하다. (다) add(0, x)로 매번 Θ(n)이라 비효율적이다.
  2. 최선은 i = n(맨 끝)으로 Θ(1), 최악은 i = 0(맨 앞)으로 Θ(n), 평균은 i가 고르게 분포할 때 약 n/2개를 옮겨 Θ(n)이다.
  3. 두 방법 모두 원소 n개를 옮기므로 증가율은 같지만 원소 하나를 옮기는 실제 비용이 다르다. 점근 표기는 상수 배 차이를 드러내지 않는다.