Finding the Next Open Slot

개념 확인: Open Addressing


한 배열 안에서 조사 순서로 빈 칸을 찾고 삭제 흔적을 관리하는 과정을 추적한다.

문제 1

load factor와 다시 넣기

  1. 원소 9개를 길이 12인 표에 저장했을 때 원소 수를 표 길이로 나눈 load factor α를 구한다.
  2. 길이 6인 표에 다음 칸을 차례로 확인하는 linear probing으로 키 8, 14, 20을 넣을 때 각 키가 확인한 인덱스와 저장 인덱스를 적는다.
  3. 표 길이를 12로 늘려 같은 순서로 다시 넣을 때 각 키의 저장 인덱스를 적는다.
  4. 기존 배열 위치를 그대로 복사하지 않고 모든 키를 다시 넣어야 하는 이유를 적는다.
해설 펼치기
  1. α = 9/12 = 0.75다.
  2. 8은 8 mod 6 = 2에 저장한다. 14는 2, 3을 확인해 3에 저장한다. 20은 2, 3, 4를 확인해 4에 저장한다.
  3. 8은 8, 14는 14 mod 12 = 2, 20은 8을 확인한 뒤 9에 저장한다.
  4. 나머지를 구하는 표 길이가 바뀌면 해시 인덱스가 달라지므로 새 길이로 조사를 다시 해야 한다. 다시 넣으면 뭉쳤던 칸도 흩어진다.
문제 2

linear probing으로 넣고 찾기

문제 2~4는 길이 m = 11인 표와 h(k) = k mod 11을 쓴다.

  1. 키 12, 23, 34, 9, 20을 차례로 넣을 때 각 키가 확인한 인덱스와 저장 인덱스를 적는다.
  2. get(34)와 get(45)가 확인하는 인덱스, 확인한 칸 수, 결과를 적는다.
  3. 다른 키가 있는 칸을 만났다는 이유만으로 탐색을 실패로 끝내면 안 되는 이유를 적는다.
  4. 클러스터링(clustering)은 활성 칸이 연속된 구간을 이루는 현상이다. 인덱스 1~3의 클러스터링이 이후 삽입에 주는 영향을 설명한다.
해설 펼치기
  1. 12는 1. 23은 1, 2를 확인해 2. 34는 1, 2, 3을 확인해 3. 9는 9. 20은 9, 10을 확인해 10이다.
  2. get(34)는 1, 2, 3을 확인해 3칸 만에 찾는다. get(45)는 45 mod 11 = 1에서 1, 2, 3, 4를 확인하고 인덱스 4가 처음부터 빈 칸이라 4칸 만에 실패한다.
  3. 충돌로 밀린 키는 조사 경로의 뒤쪽에 있으므로, 같은 키나 처음부터 빈 칸을 만날 때까지 계속 확인해야 한다.
  4. 인덱스 1~3 안에서 시작한 새 키는 시작 위치부터 구간 끝까지 지나 인덱스 4에 붙어 구간이 더 길어진다. 클러스터링 구간이 길수록 다음 빈 칸까지의 조사가 길어진다.
문제 3

조사 간격 바꾸기

문제 2의 표 길이와 해시 함수를 쓴다.

문제 2의 다섯 키를 넣은 표(인덱스 1, 2, 3, 9, 10에 키가 저장되어 있다)에서 시작한다. 1과 2는 각각 따로 생각한다.

  1. quadratic probing (h(k) + i²) mod 11로 키 45를 넣을 때 확인한 인덱스와 저장 인덱스를 적는다.
  2. double hashing (h₁(k) + i·h₂(k)) mod 11, h₂(k) = 1 + (k mod 5)로 키 56을 넣을 때 h₂(56), 확인한 인덱스, 저장 인덱스를 적는다.
  3. h₂(k)가 0이 되면 안 되는 이유를 적는다.
  4. linear probing과 비교해 double hashing이 같은 시작 인덱스를 가진 키들에 대해 달라지는 점을 적는다.
해설 펼치기
  1. h(45) = 1이다. i = 0, 1, 2에서 1, 2, 5를 확인하고 5가 비어 있어 5에 저장한다.
  2. h₂(56) = 1 + 1 = 2다. 1, 3, 5를 확인하고 5가 비어 있어 5에 저장한다.
  3. i가 늘어도 같은 인덱스만 반복해 다른 칸을 방문할 수 없다. h₂(k)는 0이 아니고 m과 서로소여야 모든 칸을 방문할 수 있다.
  4. linear probing은 시작 인덱스가 같으면 경로도 같지만, double hashing은 두 번째 해시 값이 다르면 키마다 경로가 달라진다.
문제 4

삭제와 tombstone

문제 2의 표 길이와 해시 함수를 쓴다.

문제 2의 다섯 키를 넣은 표에서 인덱스 2의 키 23을 삭제한다. tombstone은 지운 자리라는 표시다.

  1. 인덱스 2를 처음부터 빈 칸과 같은 상태로 바꾼 뒤 get(34)를 하면 어떤 결과가 나오는지 적는다.
  2. 인덱스 2에 tombstone을 남긴 뒤 get(34)가 확인하는 인덱스와 결과를 적는다.
  3. tombstone을 남긴 상태에서 키 45를 넣을 때 확인하는 인덱스와 저장 인덱스를 적는다.
  4. 삭제 직후 활성 원소 수 n과, 활성 칸과 tombstone을 합한 수 q를 적고 q를 따로 세는 이유를 적는다.
해설 펼치기
  1. 1(12)을 지나 인덱스 2가 빈 칸이라 키가 없다고 잘못 끝난다. 34는 인덱스 3에 있다.
  2. 1(다른 키), 2(tombstone은 지나감), 3에서 34를 찾는다.
  3. 1(12), 2(tombstone을 후보로 기억), 3(34), 4(처음부터 빈 칸)를 확인해 45가 없음을 확인한 뒤 기억한 인덱스 2에 저장한다.
  4. n = 4, q = 5다. tombstone도 조사 경로에 포함되어 비용을 늘리므로 재할당 판단에 함께 센다. 재할당 때는 활성 항목만 옮겨 tombstone이 사라진다.
문제 5

Python dict 확인

  1. (1, 2), "id", [1, 2] 가운데 dict 키로 쓸 수 없는 것과 이유를 적는다.
  2. d = {1: "first"} 뒤에 d[1.0] = "updated"를 실행했을 때 len(d)와 d[True]를 적는다.
  3. d = {"a": 1, "b": 2, "c": 3}에서 d["b"] = 20을 실행한 뒤와, 이어서 del d["b"] 후 d["b"] = 2를 실행한 뒤의 키 순서를 적는다.
  4. dict 탐색이 해시 값이 같은 항목을 찾은 뒤에도 키를 비교하는 이유를 적는다.
해설 펼치기
  1. [1, 2]다. list는 값이 바뀔 수 있어 hashable하지 않다. hashable 원소의 tuple과 str은 쓸 수 있다.
  2. len(d)는 1, d[True]는 "updated"다. 1, 1.0, True는 같다고 비교되어 같은 항목을 가리킨다.
  3. 값만 바꾸면 순서는 a, b, c 그대로다. 삭제 뒤 다시 넣으면 b가 마지막에 들어가 a, c, b다. 삽입 순서 보존은 키 정렬이 아니다.
  4. 서로 다른 키가 같은 조사 위치나 해시 값을 가질 수 있으므로 키 동등성까지 확인해야 Map의 의미가 보존된다.