
용어
linear search(선형검색) = sequential search(순차검색): 선형으로 늘어선 배열에서 검색하는 경우, 앞부터 순서대로 스캔하는 알고리즘
=> 정렬되지 않은 배열에서 검색할 때 사용하는 유일한 방법
sentinel method(보초법): 선형검색에서 마지막 종료조건(key가 없는경우)에 들어가는 비용을 줄여주는 방법
binary search(이진검색): 원소가 오름/내림차순으로 정렬된 배열에서 일종의 업다운 게임 방식으로 scan해야할 범위를 줄여가며 검색하는 방식
complexity(복잡도): 알고리즘의 성능을 객관적으로 평가하는 기준
time complexity(시간 복잡도): 실행하는 데 필요한 시간 평가
space complexity(공간 복잡도): 메모리와 파일 공간이 얼마나 필요한지 평가
=> 표기는 O(n), '오더 n', 'n의 오더'라고 읽음
=> 복잡도의 차원은 높은 곳을 따라감 => O(f(n)) + O(g(n)) = O(max(f(n), g(n)))
hashing(해시법): 데이터를 저장할 인덱스를 간단한 연산으로 설정하는 방법. 이를통해 데이터 검색/추가/삭제에 들어가는 비용을 줄임
hash value(해시값): 해시법을 실행하기 위해 원래 키값에 간단한 연산을 통해 얻어낸 인덱스
hash table(해시테이블): 해시값을 인덱스로하여 원소들을 새롭게 정렬해놓은 배열
hash function(해시함수): key를 hash값으로 바꿔주는 함수(로직) => 주로 나머지를 구하는 연산이나 그 응요을 사용함
bucket(버킷): 해시 테이블에서 만들어진 원소
hash collision(해시충돌): 서로 다른 키의 해시값이 같아 중복되는 현상
chaining(체인법)=open hashing(오픈해시법): 해시값이 같은 데이터를 linked list(연결리스트)로 관리하는 방법
open addressing(오픈주소법)=closed hashing(닫힌해시법)=linear probing(선형탐사법): 충돌이 발생했을 때 재해시를 통해 빈 버킷을 찾는 방법
실습
from typing import Any, Sequence
def sequential_search(arr: Sequence, key: Any) -> int:
for i in range(len(arr)):
if arr[i] == key:
return i
else:
return -1
array = [6, 8, 11, 30, 42, 5, 29, 13]
while True:
key = int(input("배열에서 찾을 값을 입력해주세요: "))
index = sequential_search(arr=array, key=key)
if index == -1:
print(f"{key}는 배열 안에 없는 값입니다.")
else:
print(f"{key}는 배열의 '{index}' 인덱스에 있습니다.")
x = input("계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): ")
if x == "y" or x == "Y": continue
else: break
# 실행결과 ==========================================
배열에서 찾을 값을 입력해주세요: 30
30는 배열의 '3' 인덱스에 있습니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): y
배열에서 찾을 값을 입력해주세요: 29
29는 배열의 '6' 인덱스에 있습니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): y
배열에서 찾을 값을 입력해주세요: 72
72는 배열 안에 없는 값입니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): q
from typing import Any, Sequence
import copy
def sequential_search(arr: Sequence, key: Any) -> int:
new_arr = copy.deepcopy(arr)
new_arr.append(key)
i = 0
while True:
if new_arr[i] == key: break
i += 1
return -1 if i == len(arr) else i
array = [6, 8, 11, 30, 42, 5, 29, 13]
while True:
key = int(input("배열에서 찾을 값을 입력해주세요: "))
index = sequential_search(arr=array, key=key)
if index == -1:
print(f"{key}는 배열 안에 없는 값입니다.")
else:
print(f"{key}는 배열의 '{index}' 인덱스에 있습니다.")
x = input("계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): ")
if x == "y" or x == "Y": continue
else: break
from typing import Any, Sequence
def binary_search(arr: Sequence, key: Any) -> int:
pointer_left = 0
pointer_right = len(arr) - 1
while True:
pointer_center = (pointer_left + pointer_right) // 2
if arr[pointer_center] == key:
return pointer_center
elif arr[pointer_center] < key:
pointer_left = pointer_center + 1
elif arr[pointer_center] > key:
pointer_right = pointer_center - 1
if pointer_left > pointer_right: break
return -1
array = [6, 8, 11, 30, 42, 58, 69, 71, 90, 98, 105, 107, 129]
while True:
key = int(input("배열에서 찾을 값을 입력해주세요: "))
index = binary_search(arr=array, key=key)
if index == -1:
print(f"{key}는 배열 안에 없는 값입니다.")
else:
print(f"{key}는 배열의 '{index}' 인덱스에 있습니다.")
x = input("계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): ")
if x == "y" or x == "Y": continue
else: break
# 실행결과 ==========================================
배열에서 찾을 값을 입력해주세요: 105
105는 배열의 '10' 인덱스에 있습니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): y
배열에서 찾을 값을 입력해주세요: 8
8는 배열의 '1' 인덱스에 있습니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): y
배열에서 찾을 값을 입력해주세요: 99
99는 배열 안에 없는 값입니다.
계속 찾으시겠습니까? (y: 계속, 다른 값 입력: 종료): q
from __future__ import annotations
from typing import Any
import hashlib
class Node:
def __init__(self, key: Any, value: Any, next: Node) -> None:
self.key = key
self.value = value
self.next = next
class ChainedHash:
def __init__(self, capacity: int) -> None:
self.capacity = capacity
self.table = [None] * capacity
def get_hash_value(self, key: Any) -> int:
if isinstance(key, int): return key % self.capacity
return(int(hashlib.sha256(str(key).encode()).hexdigest(), 16) % self.capacity)
def search(self, key: Any) -> Any:
hash = self.get_hash_value(key)
node = self.table[hash]
while node is not None:
if node.key == key: return node.value
node = node.next
return None
def add(self, key: Any, value: Any) -> bool:
hash = self.get_hash_value(key)
node = self.table[hash]
while node is not None:
if node.key == key: return False
node = node.next
new_node = Node(key, value, self.table[hash])
self.table[hash] = new_node
return True
def remove(self, key: Any) -> bool:
hash = self.get_hash_value(key)
node = self.table[hash]
prev_node = None
while node is not None:
if node.key == key:
if prev_node is None:
self.table[hash] = node.next
else:
prev_node.next = node.next
return True
prev_node = node
node = node.next
return False
def dump(self) -> None:
for i in range(self.capacity):
node = self.table[i]
print(i, end='')
while node is not None:
print(f" -> {node.key} ({node.value})", end='')
node = node.next
print()
hash = ChainedHash(13)
while True:
menu = input("수행할 작업을 선택해주세요\n(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => ")
if menu == "a" or menu == "A":
key = int(input("추가할 키를 적어주세요: "))
value = input("추가할 값을 적어주세요: ")
if hash.add(key, value):
print(f"{key}에 {value}가 추가되었습니다.")
else:
print("이미 존재하는 값입니다.")
elif menu == "r" or menu == "R":
key = int(input("삭제할 키를 적어주세요: "))
if hash.remove(key):
print(f"{key}가 삭제되었습니다.")
else:
print("테이블에 존재하지 않는 값입니다.")
elif menu == "s" or menu == "S":
key = int(input("검색할 키를 적어주세요: "))
result = hash.search(key)
if result is not None:
print(f"{key}에 해당하는 값은 {result} 입니다.")
else:
print("해당 키의 값은 테이블에 존재하지 않습니다.")
elif menu == "d" or menu == "D":
hash.dump()
elif menu == "q" or menu == "Q":
break
else:
continue
# 실행결과 ==========================================
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => r
삭제할 키를 적어주세요: 17
17가 삭제되었습니다.
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => d
0 -> 26 (39)
1 -> 40 (99)
2 -> 28 (12)
3
4 -> 30 (84)
5 -> 57 (11) -> 83 (55) -> 70 (30)
6
7
8 -> 34 (100)
9
10
11 -> 50 (77)
12 -> 38 (26)
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => s
검색할 키를 적어주세요: 76
해당 키의 값은 테이블에 존재하지 않습니다.
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => s
검색할 키를 적어주세요: 77
해당 키의 값은 테이블에 존재하지 않습니다.
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => s
검색할 키를 적어주세요: 50
50에 해당하는 값은 77 입니다.
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => q
from __future__ import annotations
from typing import Any
import hashlib
from enum import Enum
class Status(Enum):
OCCUPIED = 0
EMPTY = 1
DELETED = 2
class Bucket:
def __init__(self, key: Any = None, value: Any = None, status: Status = Status.EMPTY) -> None:
self.key = key
self.value = value
self.status = status
def set(self, key: Any, value: Any, status: Status) -> None:
self.key = key
self.value = value
self.status = status
def set_status(self, status: Status) -> None:
self.status = status
class OpenHash:
def __init__(self, capacity: int) -> None:
self.capacity = capacity
self.table = [Bucket()] * capacity
def get_hash_value(self, key: Any) -> int:
if isinstance(key, int): return key % self.capacity
return(int(hashlib.sha256(str(key).encode()).hexdigest(), 16) % self.capacity)
def get_rehash_value(self, key: Any) -> int:
return (self.get_hash_value(key) + 1) % self.capacity
def search_bucket(self, key: Any) -> Any:
hash = self.get_hash_value(key)
bucket = self.table[hash]
for _ in range(self.capacity):
if bucket.status == Status.EMPTY:
break
elif bucket.status == Status.OCCUPIED and bucket.key == key:
return bucket
hash = self.get_rehash_value(hash)
bucket = self.table[hash]
return None
def search(self, key: Any) -> Any:
bucket = self.search_bucket(key)
if bucket is not None:
return bucket.value
else:
return None
def add(self, key: Any, value: Any) -> bool:
if self.search(key) is not None:
return False
hash = self.get_hash_value(key)
bucket = self.table[hash]
for _ in range(self.capacity):
if bucket.status == Status.EMPTY or bucket.status == Status.DELETED:
self.table[hash] = Bucket(key, value, status=Status.OCCUPIED)
return True
hash = self.get_rehash_value(hash)
bucket = self.table[hash]
return False
def remove(self, key: Any) -> bool:
bucket = self.search_bucket(key)
if bucket is None:
return False
bucket.set_status(Status.DELETED)
return True
def dump(self) -> None:
for i in range(self.capacity):
bucket = self.table[i]
print(f"{i:2}. ", end='')
if bucket.status == Status.OCCUPIED:
print(f"{bucket.key} ({bucket.value})")
elif bucket.status == Status.EMPTY:
print('-- 미등록 --')
elif bucket.status == Status.DELETED:
print('-- 삭제 완료 --')
hash = OpenHash(13)
while True:
menu = input("수행할 작업을 선택해주세요\n(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => ")
if menu == "a" or menu == "A":
key = int(input("추가할 키를 적어주세요: "))
value = input("추가할 값을 적어주세요: ")
if hash.add(key, value):
print(f"{key}에 {value}가 추가되었습니다.")
else:
print("이미 존재하는 값입니다.")
elif menu == "r" or menu == "R":
key = int(input("삭제할 키를 적어주세요: "))
if hash.remove(key):
print(f"{key}가 삭제되었습니다.")
else:
print("테이블에 존재하지 않는 값입니다.")
elif menu == "s" or menu == "S":
key = int(input("검색할 키를 적어주세요: "))
result = hash.search(key)
if result is not None:
print(f"{key}에 해당하는 값은 {result} 입니다.")
else:
print("해당 키의 값은 테이블에 존재하지 않습니다.")
elif menu == "d" or menu == "D":
hash.dump()
elif menu == "q" or menu == "Q":
break
else:
continue
# 실행결과 ==========================================
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => d
0. -- 미등록 --
1. 66 (66)
2. -- 미등록 --
3. 55 (55)
4. 17 (17)
5. 44 (44)
6. 82 (82)
7. -- 삭제 완료 --
8. -- 미등록 --
9. 22 (22)
10. -- 미등록 --
11. 11 (11)
12. 77 (77)
수행할 작업을 선택해주세요
(a: 추가, r: 제거, s: 검색, d: 덤프, q: 종료) => a
추가할 키를 적어주세요: 19
추가할 값을 적어주세요: 19
19에 19가 추가되었습니다.
정리
'보초법'의 아이디어가 흥미로웠다.
길이가 n인 배열에 대해 선형탐사법을 그냥 실행할 경우
n번의 추가적인 연산이 들어가지만, 보초법의 간단한
아이디어 하나만으로 그 n번의 연산을 하지 않을 수 있었다.
'이진검색'은 마치 업다운 게임을 하는 것 같았는데,
나중에 '이진트리'를 정리하며 더 공부해봐야겠다.
'해시법'은 솔직히 생소한 개념이라 그 필요성에 대해
크게 공감하지 못했는데, 실제로 많은 서비스에서 open addressing을
통해 해시법을 사용하고 있다는 데에 놀랐다.