Search Algorithm

선형 검색

  • 선형으로 늘어선 배열에서 검색하는 경우에 원하는 키값을 가진 원소를 찾을 때까지 맨 앞부터 스캔하여 순서대로 검색하는 알고리즘
1
2
3
4
5
6
7
8
9
10
from typing import Any,Sequence

#index 반환
def seq_search(a: Sequence, key: Any) -> int:
    for i,element in enumerate(a):
        if i == len(a):
            return "no answer"
        elif element == key:
            return i 
        
1
2
3
a = [1,2,3,4,5]
key = 3
print(seq_search(a,key))
2

보초법

  • 2가지 종료 조건을 체크하는 대신 배열의 마지막에 검색하려는 값을 추가하여 비용을 반으로 줄이는 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import copy
def seq_search(a: Sequence,key : Any) -> int:
    seq = copy.deepcopy(a)
    seq.append(key)
    answer = 0

    for i,element in enumerate(seq):
        if element == key:
            answer = i
            break
    
    if answer == len(a):
        raise RuntimeError('no answer')
    else:
        print('answer : ',answer)
1
2
3
4
5
6
7
8
9
10
import time

start = time.time()

a = [1,2,3,4,5]
key = 6
seq_search(a,key)

end = time.time()
print("runtime = ",(end - start))

이진 검색

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from typing import Any,Sequence

def bin_search(a: Sequence,key: Any) -> int:
    pl = 0
    pr = len(a) - 1
    pc = len(a) // 2

    while True:
        if a[pc] > key:
            pr = pc - 1
            pc = (pl + pr) // 2

        elif a[pc] < key:
            pl = pc + 1
            pc = (pl + pr) // 2

        else: 
            return pc
        
        if pl > pr:
            raise RuntimeError('no answer')
    

    
1
2
3
4
5
6
7
8
9
10
import time

start = time.time()

a = [1,2,3,4,5]
key = 3
print('answer : ',bin_search(a,key))

end = time.time()
print("runtime = ",(end - start))
answer :  2
runtime =  0.0005581378936767578
1
2
3
4
5
6
7
8
9
10
import time

start = time.time()

a = [1,2,3,4,5]
key = 7
print('answer : ',bin_search(a,key))

end = time.time()
print("runtime = ",(end - start))

해시법

  • 데이터를 저장할 위치 = 인덱스 를 간단한 연산으로 구하는 방법

  • 특정한 해시값을 구하여 이를 인덱스로 사용하는 해시 테이블 생성

  • 해시값을 구하는 과정: 해시 함수

  • 해시 테이블에서 만들어진 원소: 버킷

  • 하지만 이러한 과정에서 새로운 원소 추가 시 버켓이 중복되는 현상을 해시 충돌 이라 한다

2가지 해시 충돌 해결법

  • 체인법: 해시값이 같은 원소를 연결 리스트로 관리

  • 오픈 주소법: 빈 버켓을 찾을 때까지 해시를 반복

체인법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from __future__ import annotations
from typing import Any,Type
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] * self.capacity

    def 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) -> int:
        hash = self.hash_value(key)
        p = self.table[hash]

        while p is not None:
            if p.value == key:
                return p.value
            p = p.next

        return None
    
    def add(self,key: Any,value : Any) -> bool:
        hash = self.hash_value(key)
        p = self.table[hash]

        while p is not None:
            if p.key == key:
                return False
            p = p.next
        
        temp = NODE(key,value,self.table[hash])
        self.table[hash] = temp
        return True
    
    def remove(self,key: Any,value : Any) -> bool:
        hash = self.hash_value(key)
        p = self.table[hash]
        pre_node = None

        while p is not None:
            if p.key == key:
                if pre_node is None:
                    self.table[hash] = p.next
                else:
                    pre_node.next = p.next
                return True
            pre_node = p
            p = p.next
        
        return False
    
    def dump(self) -> None:
        for i in range(self.capacity):
            p = self.table[i]
            print(i,end = '')
            while p is not None:
                print(f' -> {p.key}({p.value})',end = '')
                p = p.next
            print()
                

        
  • capacity : 해시 테이블의 크기(배열 table의 원소 수)

  • table : 해시 테이블을 저장하는 list형 배열

  • __init__() : 원소 수가 capacity인 list형 배열 table을 생성하고 모든 원소를 None으로 한다.

  • hash_value() : 인수 key에 대응하는 해시값 을 구한다.

    • key가 int형인 경우

      • key를 capacity로 나눈 나머지를 해시값으로 설정
    • key가 int형이 아닌경우

      • key가 정수가 아닌 경우는 그 값으로 바로 나누 수 없다.

      • 표준 라이브러리로 형 변환을 해야 해시값을 얻을 수 있다.

  • 표준 알고리즘

    • sha256 알고리즘 : 주어진 바이트 문자열의 해시값을 구하는 해시 알고리즘의 생성자.(hashlib 모듈에서 제공, RSA의 FIPS알고리즘을 바탕으로 한다.)

    • encode() 함수 : hashlib.sha256에는 바이트 문자열의 인수를 전달해야 한다. key를 str문자열로 변환한 뒤 그 문자열을 이 함수에 전달하여 바이트 문자열을 생성.

    • hexdigest() 함수 : sha256 알고리즘에서 해시값을 16진수 문자열로 꺼낸다.

    • int() 함수 : hexdigest() 함수로 꺼낸 문자열을 16진수 문자열로 하는 int형으로 변환

오픈 주소법(선형 탐사법)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from __future__ import annotations
from typing import Any,Type
from enum import Enum
import hashlib

class Status(Enum):
    OCCUPIED = 0
    EMPTY = 1
    DELETED = 2

class Bucket:
    def __init__(self,key: Any = None, value: Any = None, stat: Status = Status.EMPTY) -> None:
        self.key = key
        self.value = value
        self.stat = stat

    def set(self,key: Any, value: Any, stat: Status) -> None:
        self.key = key
        self.value = value
        self.status = stat
        
    def set_status(self,stat: Status) -> None:
        self.stat = stat

class OpenHash:
    def __init__(self,capacity: int) -> None:
        self.capacity = capacity
        self.table = [Bucket()] * self.capacity

    def 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 rehash_value(self,key: Any) -> int:
        return (self.hash_value(key + 1)) % self.capacity
    
    def search_node(self,key: Any) -> Any:
        hash = self.hash_value(key)
        p = self.table[hash]

        for i in range(self.capacity):
            if p.stat == Status.EMPTY:
                break
            elif p.stat == Status.OCCUPIED and p.key == key:
                return p
            hash = self.rehash_value(hash)
            p = self.table[hash]
        return None
    
    def search(self,key: Any) -> Any:
        p = self.search_node(key)
        if p is not None:
            return p.value
        else:
            return None
        
    def add(self,key: Any,value: Any) -> bool:
        if self.search(key) is not None:
            return False
        
        hash = self.hash_value(key)
        p = self.table[hash]
        for i in range(self.capacity):
            if p.stat == Status.EMPTY or p.stat == Status.DELETED:
                self.table[hash] = Bucket(key,value,Status.OCCUPIED)
                return True
            hash = self.rehash_value(hash)
            p = self.table[hash]
        return False

    def remove(self,key: Any) -> int:
        p = self.search_node(key)
        if p is None:
            return False
        p.set_status(Status.DELETED)
        return True
    
    def dump(self) -> None:
        for i in range(self.capacity):
            print(f'{i:2} ',end = '')
            if self.table[i].stat == Status.OCCUPIED:
                print(f'{self.table[i].key} ({self.table[i].value})')
            elif self.table[i].stat == Status.EMPTY:
                print('--미등록--')
            elif self.table[i].stat == Status.DELETED:
                print('--삭제완료--')
1

태그:

카테고리:

업데이트: