일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- OpenAI
- ChatGPT
- gpt
- 분류
- LG
- PCA
- 해커톤
- 머신러닝
- 티스토리챌린지
- 회귀
- 지도학습
- AI
- LG Aimers
- 오블완
- Machine Learning
- supervised learning
- Classification
- regression
- deep learning
- LLM
- LG Aimers 4th
- GPT-4
- 딥러닝
Archives
- Today
- Total
SYDev
[Data Structure] Chapter 13-1: 빠른 탐색을 보이는 해쉬 테이블 본문
Data Structure & Algorithm/Data Structure
[Data Structure] Chapter 13-1: 빠른 탐색을 보이는 해쉬 테이블
시데브 2023. 9. 17. 17:31빠른 탐색의 성능을 보이는 테이블 자료구조와 테이블에 의미를 부여하는
해쉬 함수를 이해하고, 두 개념이 합쳐진 해쉬 테이블을 구현해보자.
테이블 자료구조의 이해
- 테이블(Table): 저장되는 데이터의 키(key)와 값(value)이 하나의 쌍을 이루는 자료 구조를 의미한다.
- 키(key)가 존재하지 않는 '값'은 저장할 수 없다. 그리고 모든 키는 중복되지 않는다.
- 하나의 키(key)에 대응되는 값(value)는 하나이기 때문에, 테이블 자료구조의 탐색 연산은 $O(1)$의 시간 복잡도를 가진다.
배열을 기반으로 하는 테이블
#include <stdio.h>
typedef struct _empInfo
{
int empNum;
int age;
} EmpInfo;
int main(void)
{
EmpInfo empInfoArr[1000];
EmpInfo ei;
int eNum;
printf("사번과 나이 입력: ");
scanf("%d %d", &(ei.empNum), &(ei.age));
empInfoArr[ei.empNum] = ei;
printf("확인하고픈 직원의 사번 입력: ");
scanf("%d", &eNum);
ei = empInfoArr[eNum];
printf("사번 %d, 나이 %d \n", ei.empNum, ei.age);
return 0;
}
사번과 나이 입력: 0917
23
확인하고픈 직원의 사번 입력: 0917
사번 917, 나이 23
- 이와 같은 형태의 테이블에서는 고유번호의 범위가 커질수록 큰 배열이 필요해진다.
- 이런 문제를 해결해주는 것이 '해쉬' 개념이다.
해쉬 함수와 충돌 문제
해쉬 함수
해쉬 함수는 다음 두 가지 문제를 해결할 수 있다.
- key의 범위가 배열의 인덱스 값으로 사용하기에 적당하지 않다.
- 직원 고유번호의 범위를 수용할 수 있는 매우 큰 배열이 필요하다.
위 예제에 해쉬 함수를 추가하여 다시 구현해보자.
#include <stdio.h>
typedef struct _empInfo
{
int empNum;
int age;
} EmpInfo;
int GetHashValue(int empNum)
{
return empNum % 100;
}
int main(void)
{
EmpInfo empInfoArr[100];
EmpInfo emp1 = {20120003, 42};
EmpInfo emp2 = {20130012, 33};
EmpInfo emp3 = {20170049, 27};
EmpInfo r1, r2, r3;
empInfoArr[GetHashValue(emp1.empNum)] = emp1;
empInfoArr[GetHashValue(emp2.empNum)] = emp2;
empInfoArr[GetHashValue(emp3.empNum)] = emp3;
r1 = empInfoArr[GetHashValue(20120003)];
r2 = empInfoArr[GetHashValue(20130012)];
r3 = empInfoArr[GetHashValue(20170049)];
printf("사번 %d, 나이 %d \n", r1.empNum, r1.age);
printf("사번 %d, 나이 %d \n", r2.empNum, r2.age);
printf("사번 %d, 나이 %d \n", r3.empNum, r3.age);
return 0;
}
사번 20120003, 나이 42
사번 20130012, 나이 33
사번 20170049, 나이 27
충돌 문제
다음과 같이 해쉬 함수를 거쳤을 때, 중복되는 해쉬 값이 출력되는 것을 충돌(collision) 문제라고 한다.
어느 정도 갖춰진 테이블과 해쉬의 구현
파일명: Person.h
#ifndef __PERSON_H__
#define __PERSON_H__
#define STR_LEN 50
typedef struct _person
{
int ssn; //주민등록번호
char name[STR_LEN];
char addr[STR_LEN];
} Person;
int GetSSN(Person * p);
void ShowPerInfo(Person * p);
Person * MakePersonData(int ssn, char * name, char * addr);
#endif
파일명: Person.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Person.h"
int GetSSN(Person * p)
{
return p->ssn;
}
void ShowPerInfo(Person * p)
{
printf("주민등록번호: %d \n", p->ssn);
printf("이름: %s \n", p->name);
printf("주소: %s \n\n", p->addr);
}
Person * MakePersonData(int ssn, char * name, char * addr)
{
Person * newP = (Person*)malloc(sizeof(Person));
newP->ssn = ssn;
strcpy(newP->name, name);
strcpy(newP->addr, addr);
return newP;
}
파일명: Slot.h
#ifndef __SLOT_H__
#define __SLOT_H__
#include "Person.h"
typedef int Key; //주민등록번호
typedef Person * Value;
enum SlotStatus {EMPTY, DELETED, INUSE}; //슬롯의 상태를 나타내는 열거형 선언
typedef struct _slot
{
Key key;
Value val;
enum SlotStatus status;
} Slot;
#endif
파일명: Table.h
#ifndef __TABLE_H__
#define __TABLE_H__
#include "Slot.h"
#define MAX_TBL 100
typedef int HashFunc(Key k); //책에 잘못 써있음, 아래에서 HashFunc을 받을 때 *를 이용하기 때문에 여기선 그냥 함수의 형태로 와야 함
/***
typedef int HashFunc(Key k);
위와 같은 선언에 대해서는 이후에 아래와 같이 이용할 수 있음
MyHashFunc이라는 함수가 선언되었다고 가정
HashFunc * f = MyHashFunc
혹은 MyFunc(HashFunc * f); 형태로 매개변수로도 받을 수 있음
***/
typedef struct _table
{
Slot tbl[MAX_TBL];
HashFunc * hf;
} Table;
void TBLInit(Table * pt, HashFunc * f);
void TBLInsert(Table * pt, Key k, Value v);
Value TBLDelete(Table * pt, Key k);
Value TBLSearch(Table * pt, Key k);
#endif
-> 책 오탈자로 한참을 찾았다.. 포인터 개념을 확실히 숙지하면 헷갈리지 않을 문제!
파일명: Table.c
#include <stdio.h>
#include <stdlib.h>
#include "Table.h"
void TBLInit(Table * pt, HashFunc * f)
{
int i;
for(i=0; i<MAX_TBL; i++)
(pt->tbl[i]).status = EMPTY;
pt->hf = f;
}
void TBLInsert(Table * pt, Key k, Value v)
{
int hv = pt->hf(k); //hash value, hash function
pt->tbl[hv].val = v;
pt->tbl[hv].key = k;
pt->tbl[hv].status = INUSE;;
}
Value TBLDelete(Table * pt, Key k)
{
int hv = pt->hf(k);
if((pt->tbl[hv]).status != INUSE)
{
return NULL;
}
else
{
(pt->tbl[hv]).status = DELETED;
return (pt->tbl[hv]).val;
}
}
Value TBLSearch(Table * pt, Key k)
{
int hv = pt->hf(k);
if((pt->tbl[hv]).status != INUSE)
return NULL;
else
return (pt->tbl[hv]).val;
}
파일명: SimpleHashMain.c
#include <stdio.h>
#include <stdlib.h>
#include "Person.h"
#include "Table.h"
int MyHashFunc(int k)
{
return k % 100;
}
int main(void)
{
Table myTbl;
Person * np;
Person * sp;
Person * rp;
TBLInit(&myTbl, MyHashFunc);
np = MakePersonData(20120003, "Lee", "Seoul");
TBLInsert(&myTbl, GetSSN(np), np);
np = MakePersonData(20130012, "KIM", "Jeju");
TBLInsert(&myTbl, GetSSN(np), np);
np = MakePersonData(20170049, "HAN", "Kangwon");
TBLInsert(&myTbl, GetSSN(np), np);
sp = TBLSearch(&myTbl, 20120003);
if(sp != NULL)
ShowPerInfo(sp);
sp = TBLSearch(&myTbl, 20130012);
if(sp != NULL)
ShowPerInfo(sp);
sp = TBLSearch(&myTbl, 20170049);
if(sp != NULL)
ShowPerInfo(sp);
rp = TBLDelete(&myTbl, 20120003);
if(rp != NULL)
free(rp);
rp = TBLDelete(&myTbl, 20130012);
if(rp != NULL)
free(rp);
rp = TBLDelete(&myTbl, 20170049);
if(rp != NULL)
free(rp);
return 0;
}
주민등록번호: 20120003
이름: Lee
주소: Seoul
주민등록번호: 20130012
이름: KIM
주소: Jeju
주민등록번호: 20170049
이름: HAN
주소: Kangwon
좋은 해쉬 함수의 조건
좋은 해쉬 함수는 키의 일부분을 참조하여 해쉬 값을 만들지 않고, 키 전체를 참조하여 해쉬 값을 만든다.
자릿수 선택 방법
- 자릿수 선택(Digit Selection) 방법: 키의 특정 위치에서 중복의 비율이 높거나, 아예 공통으로 들어가는 값이 있다면, 이를 제외한 나머지를 가지고 해쉬 값을 생성하는 방법이다.
자릿수 폴딩 방법
다음과 같이 273419라는 숫자가 있을 때, 두 자리씩 숫자를 접어서 겹치는 숫자를 모두 더하여, 해쉬 값을 얻어내는 방법이 자릿수 폴딩(Digit Folding) 방법이다.
이외에도 폴딩의 과정에서 덧셈 대신 XOR 연산을 진행하는 방법 등 여러가지 방법이 있으며, 상황에 맞는 적절한 방법을 사용하면 된다.
- 윤성우, <윤성우의 열혈 자료구조>, 오렌지미디어, 2022.04.11
'Data Structure & Algorithm > Data Structure' 카테고리의 다른 글
[Data Structure] Chapter 14: 그래프(Graph) (0) | 2023.09.20 |
---|---|
[Data Structure] Chapter 13-2: 충돌(Collision) 문제의 해결책 (0) | 2023.09.18 |
[Data Structure] Chapter 12: 균형 잡힌 이진 탐색 트리 - AVL 트리 (0) | 2023.09.13 |
[Data Structure] Chapter 11-2: 이진 탐색 트리 (0) | 2023.09.12 |
[Data Structure] Chapter 11-1: 탐색의 이해와 보간 탐색 (0) | 2023.09.11 |