1. 해시법 정의하기
해시법(hashing)
- 데이터를 저장할 위치(인덱스)를 간단한 연산으로 구하는 것
해시값(hash value)
- ex) 배열의 키 값(각 요솟값)을 배열의 요소 개수 13으로 나눈 나머지
- 데이터에 접근할 때 사용
해시 테이블 (hash table)
- 해시 값이 인덱스가 되도록 원래의 키 값을 저장한 배열
해시 함수(hash function)
- 키값을 가지고 해시값을 만드는 과정
- 나머지를 구하는 연산 또는 나머지 연산을 다시 응용한 연산을 사용
버킷(bucket)
- 해시 테이블의 각 요소
2. 충돌 살펴보기
충돌(collision)
- 저장할 버킷이 중복되는 현상
ex) 새로운 값 18 추가 (해시값 5 -> 버킷 a[5])
but a[5]에는 다른 값 저장되어 있음
충돌에 대한 대처
1) 체인법
- 같은 해시값을 갖는 요소를 연결 리스트로 관리
2) 오픈 주소법
- 빈 버킷을 찾을 때까지 해시를 반복
2. 체인법 살펴보기
체인법(chaning)
- 같은 해시값을 갖는 데이터를 쇠사슬(chain) 모양으로 연결 리스트에서 연결하는 방법
- 오픈 해시법
2.1 같은 해시값을 갖는 데이터 저장
- 배열의 각 버킷(해시 테이블)에 저장하는 값
-> 그 인덱스를 해시값으로 하는 연결 리스트의 첫 번째 노드(node)에 대한 포인터
2.2 버킷용 구조체 Node
- 개별 버킷의 자료형을 나타냄
//버킷을 나타내는 노드
typedef struct __node {
Member data; //데이터
struct __node *next; //다음 노드에 대한 포인터
}Node;
2.3 해시 테이블을 관리하는 구조체 ChainHash
- 해시 테이블을 관리하기 위한 구조체
//해시 테이블
typedef struct {
int size; //해시 테이블의 크기
Node **table; //해시 테이블의 첫 번째 요소에 대한 포인터
}ChainHash;
2.4 해시값을 구하는 hash 함수
- 매개변수 key로 받은 회원번호값을 해시 테이블의 크기 size로 나눈 나머지를 반환
//해시 함수 (key의 해시값을 반환)
static int hash(int key, int size)
{
return key % size;
}
2.5 노드의 값을 설정하는 SetNode 함수
- 버킷의 노드에 값을 설정
//노드의 각 멤버에 값을 설정
static void SetNode(Node *n, const Member *x, const Node *next)
{
n->data = *x; //데이터
n->next = (Node*) next; //다음 노드에 대한 포인터
}
2.6 해시 테이블을 초기화 하는 Initialize 함수
- 공백인 해시 테이블을 만듬
- 요소의 개수가 size인 배열 table의 본체를 생성하고
매개변수 size로 받은 값을 멤버 size에 복사
//해시 테이블 초기화
int Initialize(ChainHash *h, int size)
{
if((h->table = (Node**)calloc(size, sizeof(Node *))) == NULL)
{
h->size = 0;
return 0;
}
h->size = size;
for(int i = 0; i <size; i++) //모든 버킷을 공백 상태로 만듬
h->table[i] = NULL;
return 1;
}
2.7 키값으로 요소를 검색하는 Search 함수
- 키 값이 x->no인 요소를 검색하는 함수
<검색 알고리즘>
1) 해시 함수가 키값을 해시값으로 변환
2) 해시값을 인덱스로 하는 버킷 선택
3) 선택한 버킷의 연결리스트를 처음부터 순서대로 검사
키값과 같은 값을 찾으면 검색성공
ex) 33을 검색
- 33의 해시값은 7
- table[7]이 가리키는 연결 리스트를 하나씩 끌어당기며 찾음
ex) 26을 검색
- 26의 해시값은 0
- table[0]이 NULL이므로 검색 실패
//검색
Node *Search(const ChainHash *h, const Member *x)
{
int key = hash(x->no, h->size); //검색하는 데이터의 해시값
Node *p = h->table[key]; //현재 선택한 노드
while(p != NULL)
{
if(p->data.no == x->no) //검색 성공
return p;
p= p->next;
}
return NULL; //검색 실패
}
2.8 요소를 추가하는 Add 함수
- 포인터 x가 가리키는 데이터를 추가하는 함수
<추가 알고리즘>
1) 해시 함수가 키값을 해시값으로 변환
2) 해시값을 인덱스로 하는 버킷 선택
3) 버킷에 저장된 포인터가 가리키는 연결 리스트를 처음부터 순서대로 검색
키값과 같은 값을 찾으면 키 값이 이미 등록된 상태이므로 추가 실패
끝까지 스캔하여 찾지 못하면 리스트의 맨 앞 위치에 노드 삽입
ex) 13 추가
- 13의 해시값은 0
- table[0]은 NULL
- 그 노드에 대한 포인터를 table[0]에 대입
ex) 46 추가
- 46의 해시값은 7
- table[7]의 버킷에는 20과 33을 연결하는 리스트의 포인터가 저장
- 리스트의 맨앞에 46 추가
- 추가할 값 46을 저장하는 노드를 새로 만들고 그 노드에 대한 포인터를 table[7]에 대입
// 데이터 추가
int Add(ChainHash *h, const Member *x)
{
int key = hash(x->no, h->size); //추가하는 데이터의 해시값
Node *p = h->table[key]; //현재 선택한 노드
Node *temp;
while(p != NULL) {
if(p->data.no == x->no) //이 키는 이미 등록됨
return 1; //추가 실패
p = p->next;
}
if((temp =(Node*) calloc(1,sizeof(Node))) == NULL)
return 2;
SetNode(temp,x,h->table[key]); //추가하는 노드에 값을 설정
//현재 해시값을 인덱스로 하는 연결리스트의 head로 지정해서 연결리스트의 맨 앞에 추가
h->table[key] = temp; //추가 성공 table[key] => 연결리스트의 head 역할
return 0;
}
2.9 요소를 삭제하는 Remove 함수
- 키 값이 x->no인 요소를 삭제하는 함수
<삭제 알고리즘>
1) 해시 함수가 키값을 해시값으로 변환
2) 해시값을 인덱스로 하는 버킷을 선택
3) 선택한 버킷의 포인터가 가리키는 연결 리스트를 처음부터 순서대로 검색
키값과 같은 값을 찾으면 그 노드를 리스트에서 삭제
ex) 69를 삭제
- 69의 해시값은 4
- table[4]의 버킷에 저장된 포인터의 리스트를 연결 검색
- 69를 저장한 노드의 다음 노드는 17을 저장한 노드
- 17을 저장한 노드에 대한 포인터를 table[4]의 버킷에 대입
- 69를 저장하는 노드의 메모리 해제
//데이터 삭제
int Remove(ChainHash *h, const Member *x)
{
int key = hash(x->no, h->size ); //삭제하는 데이터의 해시값
Node *p = h->table[key]; //현재 선택한 노드
Node **pp = &h->table[key]; //현재 선택한 노드에 대한 포인터
while(p != NULL) {
if(p->data.no == x->no) { //찾으면
*pp = p->next;
free(p); //해제
return 0; //삭제 성공
}
pp = &p->next;
p = p->next; //다음 노드를 선택
}
return 1; //삭제 실패 (존재하지 않음)
}
2.10 해시 테이블의 내용을 출력하는 Dump 함수
- 해시 테이블의 내용을 통째로 출력
//해시 테이블 덤프
void Dump(const ChainHash *h)
{
for(int i=0; i <h->size; i++)
{
Node *p = h->table[i];
printf("%02d",i);
while (p != NULL) {
printf("->%d (%s)", p->data.no, p->data.name);
p = p->next;
}
putchar('\n');
}
}
2.11 모든 데이터를 삭제하는 Clear 함수
- 해시 테이블에 등록된 모든 데이터를 삭제
<데이터 삭제 알고리즘>
1) 배열 표의 요소가 NULL이 아니면 그 해시값을 갖는 데이터가 연결 리스트로 존재하므로,
연결 리스트를 맨 앞부터 순서대로 검사하면서 리스트의 모든 노드에 대한 메모리 해제
2) 검사 중인 배열 요소에 NULL을 대입
3) 배열에 대한 모든 검사가 끝나면 모든 버킷이 "공백"상태가 됨
//모든 데이터 삭제
void Clear(ChainHash *h)
{
for(int i=0; i <h->size; i++) {
Node *p = h->table[i]; //현재 선택한 노드
while(p != NULL) {
Node *next = p->next;
free(p); //현재 선택한 노드의 메모리 해제
p = next; //다음 노드 선택
}
h->table[i] = NULL;
}
}
2.12 해시 테이블을 종료하는 Terminate 함수
- 해시 테이블의 사용을 마칠때 사용
//해시 테이블 종료
void Terminate(ChainHash *h)
{
Clear(h); //모든 데이터 삭제
free(h->table); //해시 테이블 배열의 메모리 해제
h->size = 0; //해시 테이블 크기를 0으로 초기화
}
'C언어 > 자료구조' 카테고리의 다른 글
10-1 장 해시법 (2) (0) | 2023.08.06 |
---|---|
9-2장 이진 트리와 이진 검색 트리 (0) | 2023.08.04 |
9-1장 트리 (0) | 2023.08.03 |
8-4장 원형 이중 연결 리스트 (0) | 2023.08.03 |
8-3장 커서를 이용한 연결 리스트 (0) | 2023.08.03 |