(강의 내용 정리에 사용되는 자료의 출처는 저자님의 강의입니다. 출처 - 코딩 테스트 합격자 되기 [박경록])
(자료를 참고해서 제가 시각적으로 더 보기 편하게 편집한 사진들도 있습니다.)
대학원 서류 접수 기간이라, 지금 해놓지 않으면 또 미뤄질 것 같아서 미리 수강하고 정리를 해봤습니다!
▶ 강의 목차
- 해시의 개념
- 해시 함수
- 충돌 처리
- 실제 예시
▶ 강의 내용 정리
- 배열로 구현한 연락처
- 이름 → 연락처 (이름에 해당되는 연락처 저장)
- 만약에 홍길동이라는 사람의 연락처를 알고 싶다면 어떻게 해야 해?
- 이름 테이블 "홍길동" 선형 탐색
- 그 위치에 해당되는 전화테이블을 참조
- 특정 데이터의 위치를 판단할 수 있는 정보가 없으므로, 선형탐색을 해야 함
- 이 배열에서는 [이름 5] 까지 가려면, 순차적으로 0부터 4까지 탐색해야 함
- 비효율적임!
- 인덱스에 이름정보를 어떻게 넣을 것인가?
- 홍길동 이름보고 해시함수를 통해서 어느 배열 인덱스 위치에 저장되는지 알 수 있게 해 보자
- 선형 탐색보다 조금 더 빠르게!
- 홍길동 이름보고 해시함수를 통해서 어느 배열 인덱스 위치에 저장되는지 알 수 있게 해 보자
- 해시를 적용한 연락처
- 해시 함수를 사용해서 변환한 값을 인덱스로 삼아 키-값을 저장해서 빠른 데이터 탐색을 제공하는 자료구조
- 키를 해시 함수에 넣으면 인덱스가 나오는 자료구조
- 아래 사진으로 이해를 해보자!
- "함진아"라는 키에 해시 함수를 적용하면 인덱스 2를 얻는다.
- 몇 번을 해봐도 "함진아"의 해시 함수 적용값은 2가 된다.
- 이름을 가지고 바로 연락처 위치를 알 수 있다.
- 이름 자체로 인덱스를 구할 수 있다.
- 아까 배열에서 선형탐색할 때는 데이터 개수가 N개라면 O(N)이었지만
- 해시를 이용한 경우 이름 자체로 인덱스를 구할 수 있으므로, O(1)이다!!
- 해시의 요소 3가지 = 해시 함수, 버킷(테이블의 하나하나), 해시테이블
- 해시함수(정의)
- 임의의 키를 해시 테이블의 인덱스로 변경해 주는 함수
- 2가지 키 포인트!
- 해시 테이블의 크기가 N이라면 해시 함수는 0 ~ (N-1) 사이 값을 내야 함
- 충동을 최소화할수록 좋은 해시 함수
- "홍길동"과 "홍길서"는 다른 키 임
- 서로 다른 키에 대해 해시 함수가 동일한 인덱스를 반환할 경우 충돌이 발생
- 해시함수(나눗셈 법)
- h(x) = x mod k (x는 키, k는 소수) (x를 k로 나눈 나머지 값을 인덱스 값으로 사용)
- k로 나눈 나머지는 0 ~ k-1이므로 해시 테이블의 크기는 k
- k가 소수인 이유는 충돌을 줄이기 위함(소수가 아니면 k주기로 해시값 반복 됨. 책에 자세하게 설명 있음!)
- mod가 뭐죠?
- 7 % 4 = 3 (%를 mod라고 생각하면 된다구~)
- 7/4 = 몫이 1이고, 나머지 3
- 조금 수학적으로 보는 예시. 7 % 2
- 2로 나눈 나머지
- 짝수는 무조건 0
- 홀수는 무조건 1
- 0 or 1이니까 충돌이 엄청 많이 발생할 수밖에 없음
- 해시 테이블의 크기가 K인데, 0,1 만 사용할 수 있다.
- 소수 경우에는 나누어 떨어질 수 없으니, 소수를 많이 사용함
- 대신 단점도 있음
- 해시 테이블의 크기가 커디면 더 큰 소수가 필요함
- 해시 테이블의 크기가 K이면 0 ~ K-1 까지 나오는 소수가 있어야 함(MOD 했을 때)
- 소수 자체를 큰 것을 찾는 게 쉽지가 않아서 단점
- 해시함수(곱셈법, 정의)
- 나눗셈법은 k가 소수여야 하므로, 테이블 크기가 커지면.. 큰 소수가 필요함(어려움)
- 그래서 나온 것이 곱셈법!
- 곱셈법은 소수를 활용하지 않고 황금비를 곱하는 방식
- h(x) = ((x*A)mod 1)*m)
- x는 키, A는 황금 비, m은 최대 버킷의 개수
- 해시함수(곱셈법, 황금비란?)
- 아래 사진처럼 구한 황금비에서 우리가 사용할 부분은 소수점 부분!
- 황금비 외울 필요는 없지만 여러 곳에 쓰임. 궁금하면 위키피디아에 검색
- 해시함수(곱셈법, 실제연산)
- h(x) = ((x*A) mod 1) *m)
- x는 키, A는 황금 비, m은 최대 버킷의 개수
- 키 값에 A를 곱함
- 1.7이면 정수는 1 소수는 7
- mod 1을 하면 소수부분만 취함
- (x*A) mod 1) 했을 때 나올 수 있는 결과는 0 ~ 0.99999999999
- 그 결과에 m을 곱하면
- 0 ~ m-1.xxxxxxxxxxx
- 이 부분(정수 부분)을 취해서 인덱스에 활용
- 아래 사진에 있는 표 참고!
- 곱셈법이 조금 더 복잡하지만, 소수가 필요 없다는 점에 좋음
- h(x) = ((x*A) mod 1) *m)
- 해시함수(문자열 해싱)
- 지금까지는 키로 주어졌지만, 문자로 주어지면 어떻게 하는지 알아보도록 하자
- 문자열의 아스키코드 값을 활용한 해싱
- 해시함수(문자열 해싱, 해시함수 설명)
- 공식을 한번 풀어봅시다~!
- p는 31 (홀수이면서 메르센 소수. 값을 균등하게 퍼뜨리기 위한)
- 숫자가 금방 커지므로 구현시 overflow가 발생할 수 있음
- 예를 들면 31의 50승 이렇게 구해라 하면 오버플로우 발생
- 구현할 때는 overflow 생각해야 함
- 그래서 수정된 공식 사용
- a = 1, b = 2, c = 3, ..... z = 26로 숫자를 매칭해서 사용
- 각 문자마다 숫자를 하나씩 매칭
- 수정된 공식
- 해시 충돌이란?
- 서로 다른 키에 대해 해시 함수 결괏값이 같은 경우 [충돌]이 발생함
- 체이닝, 개방 주소법이 충돌을 처리하는 대표적인 방법
- 서로 다른 키에 대해 해시 함수 결괏값이 같은 경우 [충돌]이 발생함
- 해시충돌처리(체이닝)
- 충돌 발생 시, 해당 버킷에 링크드리스트로 데이터를 연결함
- 특정 버킷에 데이터가 N개인 경우, 원하는 값을 찾는데 O(N)이 걸릴 수 있음
- 최악의 경우
- 데이터가 N개 있는데 전부 충돌이 되었다!
- N번째 원소 찾는데 O(N)이다.
- 근데 이런 경우는 드물다
- 실제로는 이를 좀 더 효율적으로 하는 방법도 가능하나(선형 + 이진트리) 체이닝의 특징을 설명하기 위해 간소화함
- But. 우리는 코테 준비를 하는 것이기 때문에 나중에 관심 있으면 심도 있게 공부하는 걸로
- 해시충돌처리(개방 주소법 - 선형 탐사)
- 아까 체이닝에서는 해시테이블 외 추가 공간을 사용했지만, 개방 주소법은 해시 테이블 내에서 해결하려고 한다!
- 최대한 기존 테이블을 사용하자는 방식
- 체이닝 기법보다는 메모리를 아낄 수 있음
- 충돌 발생 시, 다른 빈 버킷을 찾아 충돌값을 삽입
- 최대한 기존 테이블을 사용하자는 방식(메모리를 아낄 수 있음)
- k는 키, i는 충돌 시 이동할 버킷 수, m은 버킷 사이즈
- 아까 체이닝에서는 해시테이블 외 추가 공간을 사용했지만, 개방 주소법은 해시 테이블 내에서 해결하려고 한다!
- 해시충돌처리(개방 주소법 - 이중해싱)
- 용어 그대로 해시 함수를 2개 사용(경우에 따라 N개까지 확장)
- 기존에 i를 더했던 선형탐사와 다르게 2번째 해시함수에 i를 곱하는 방식도 가능
- 이때 클러스터를 줄이기 위해 m을 제곱수나 소수로 함
- k는 키, i는 임의의 수, h1은 첫 번째 해시함수, h2는 두 번째 해시함수, m은 테이블 크기
- 해시함수를 2개 적용하고 두번째 해시함수에 i를 곱한다~
- 지금까지 배운 것 정리
- 해시개념
- 해시함수
- 나눗셈법
- 곱셈법
- 문자열 해싱
- 해싱충돌처리
- 체이닝
- 개방 주소법
- 선형탐사
- 이중해싱
- 그러면 언제 해시를 활용해서 문제를 풀어야 하나?
- 키- 값 쌍으로 연관된 데이터가 존재하며, 해당 값에 대한 접근이 빈번한 경우
- 예를 들어 사전(단어를 검색해서 뜻을 찾음)이나
- 연락처(이름을 검색해서 번호를 찾음)
- 코테에서는 키랑 값을 잘 설정해야 함!!!
- 이름(키) - 전화번호(값). 이름을 통해 전화번호를 검색한다. 이렇게 해야 하는데
- 초보자의 경우 전화번호(키) - 이름(값)으로 해버리는 대형사고가 난다!!!
- 이렇게 해버리면 해시를 사용하는 의미가 없음. 선형 탐색이 되어버린다...
- 중복되지 않는 키를 사용하는 경우
- 예를 들어 학번이나 집주소(중복되지 않음)
- '키 1'이 있고 이에 해당하는 '값 1'이 있음
- '키 1'에 '값 2'가 있다고 해버리면 기존에 해당하는 값이 없어지고 대체된다! 조심해야 함
- 키- 값 쌍으로 연관된 데이터가 존재하며, 해당 값에 대한 접근이 빈번한 경우
- 언제 해시를 활용해서 문제를 풀어야 하나? (예시 1)
- 학생 정보 관리 시스템
- 학생들의 정보를 관리하는 시스템을 예로 들어보겠습니다.
- 학생의 ID를 키로, 성적을 값으로 저장한다고 가정합니다.
- (코드 부분은 C++이라 생략하겠습니다!)
- 가장 많이 헷갈리는 부분
- map을 해시로 착각하는 것
- 해시(x), 이진탐색트리를 활용한 것
- 매번 키 순으로 데이터가 정렬됨 O(logN) → TLE 발생할 수 있음
- 예시를 들어보자!
- 2중 반복문 안에 map에 데이터 추가 연산 O(N^2logN)
- 키 값 정렬이 필요하지 않은데 map을 쓰면 안 됩니다.
- 해시 문제는 map이 아니라 unordered_map
- ID 학점
- "12345" "A"
- "67890" "B"
- "54321" "A+"
- 값의 타입을 벡터로?
- ID 학점
- "Alice" 90 85 평균을 구해야 하니까 나누는 건 size를 이용하면 편함
- "Bob" 78 82 88
- 언제 해시를 활용해서 문제를 풀어야 하나? (예시 2)
- 문자열이 입력됩니다.
- 문자열의 각 문자의 개수를 확인하고 싶습니다.
- (코드 부분은 생략하겠습니다)
- 키는 char형으로, 개수는 int형으로 입력받음
- 이외에는 파이썬과 헷갈릴 수 있으므로 생략하겠습니다!
- 해시를 사용하지 말아야 하는 경우
- 특정 키에 여러 가지 값을 매칭해야 하는 경우
- 앞서 이야기했던 것과 같음!
▶ 강의 후기
-
- 배열은 코테를 풀면서 사용해봤는데, 해시라는 개념은 한번도 적용해보지 못했습니다.
- 저번 강의와 마찬가지로 저자님이 전달하고자 하는 개념은 이해했습니다.
- 하지만 강의와 스터디의 목적이 코딩 테스트 문제를 푸는 것인 만큼 파이썬 편의 책을 참고해서 문제를 풀어보며 직접 적용하는 시간이 필요할 것 같습니다.
- 수강하고 천장을 보면서 이걸 설명할 수 있을까? 생각해봤는데 해시함수 공식 부분과 공식을 수정해야하는 이유 그리고 수정한 부분에 대한 설명까지는 힘들 것 같습니다. (해당 부분에 복습이 필요한...)
▶ 궁금한 점
- 당장은 없는데 복습과 코테 문제를 풀면서 생길 것 같습니다
- 생기는대로 추가로 작성해 보겠습니다.
'그룹 스터디(책 기반으로) > 코딩테스트 합격자 되기 C++' 카테고리의 다른 글
코테 스터디 4주차 [트리] (1) | 2024.11.18 |
---|---|
혼자 읽기 아쉬운 저자님의 당부사항 (1) | 2024.10.16 |
코테 스터디 2주차 [스택/큐] (3) | 2024.10.15 |