13장 테이블(Table)과 해쉬(Hash)
- 테이블 : 원하는 바를 단번에 찾아내는 방식
- 키(Key)와 값(Value)의 한 쌍으로 저장되는 구조
- 시간복잡도 : O(1)
- 테이블 소스 : S013_Table.c
12345678910111213141516171819202122232425262728#include <stdio.h>int main(void){int i;int empInfoArr[1000]; //사번은 0~999번까지만 가능int num, age;// for(i = 0; i < 5; i++){// printf("사번(0~999)과 나이(0~) 입력: ");// scanf("%d %d", &num, &age);// empInfoArr[num] = age; // 단번에 저장!// }int numArr[5] = {100, 300, 250, 450, 780};int ageArr[5] = {56, 45, 47, 35, 28};for(i = 0; i < 5; i++){empInfoArr[numArr[i]] = ageArr[i];}for(i = 0; i < 2; i++){printf("확인하고픈 직원의 사번(0~999) 입력: ");scanf("%d", &num);age = empInfoArr[num]; // 단번에 탐색!printf("사번 %d, 나이 %d \n", num, age);}return 0;} - 테이블의 문제점
- 사원번호가 : 201900000 ~ 201900099으로 100명이 근무하는 회사에 대해 프로그래밍 한다면 위 코드를 어떻게 수정해야 하는가?
- 해쉬(Hash) 코드 사용 샘플 소스 : S013_TableHash.c
123456789101112131415161718192021222324252627#include <stdio.h>int getHashValue(int num){return num % 100;}int main(void){int i;int empInfoArr[100]; //사번은 0~99번까지만 가능int num, age;int numArr[5] = {201900010, 201900030, 201900025, 201900045, 201900078};int ageArr[5] = {56, 45, 47, 35, 28};for(i = 0; i < 5; i++){empInfoArr[getHashValue(numArr[i])] = ageArr[i];}for(i = 0; i < 2; i++){printf("확인하고픈 직원의 사번(0~999) 입력: ");scanf("%d", &num);age = empInfoArr[getHashValue(num)]; // 단번에 탐색!printf("사번 %d, 나이 %d \n", num, age);}return 0;} - 해쉬(Hash) 방법
- 자릿수 선택(Digit Selection)
- 같은 학과 학생들로 구성된 학과 학생들의 학번
- 학과가 다른 학생들로 구성된 동아리 구성원들의 학번
- 자릿수 폴딩(Digit Folding)
- 자릿수 선택(Digit Selection)
- 좋은 해쉬 함수의 조건
- 슬롯의 사용 영역이 고르게 분포되어 있어야 한다.
- 충돌(Collision) 문제의 해결책
- 열린 어드레싱 방법(open addressing method)
- 선형 조사법(Linear Probing) : 충돌이 발생하면 바로 다음 위치에 저장
- 이차 조사법(Quadratic Probing) : 충돌이 발생하면 제곱만큼 떨어진 위치에 저장
- 이중 해쉬(Double Hash) : 충돌이 발생하면 몇칸 뒤에 저장할 것인지를 다시 해쉬를 이용해서 구함
- 닫힌 어드레싱 방법(closed addressing method)
- 체이닝(Chaining)
- 배열과 연결리스트로 구현 가능하지만 배열은 완전한 체이닝을 만들 수 없음
- 보통 연결리스트로 구현
- 체이닝(Chaining)
- 열린 어드레싱 방법(open addressing method)
- 단순 해쉬 완성 버전 : S013_SimpleHashTable.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157#include <stdio.h>#include <stdlib.h>////////// Person Start#define STR_LEN 50typedef struct _person{int ssn; // 주민등록번호char name[STR_LEN]; // 이름char addr[STR_LEN]; // 주소} Person;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 End////////// Slot Starttypedef int Key;typedef Person * Value;enum SlotStatus {EMPTY, DELETED, INUSE};typedef struct _slot{Key key;Value val;enum SlotStatus status;}Slot;////////// Slot End////////// Table Start#define MAX_TBL 100typedef int HashFunc(Key k);typedef struct _table{Slot tbl[MAX_TBL];HashFunc * hf;}Table;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);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;elsereturn (pt->tbl[hv]).val;}////////// Table EndPerson * 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;}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;} - 체이닝 해쉬 완성 버전 : S013_ChainedHashTable.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316#include <stdio.h>#include <stdlib.h>#include <string.h>////////// Person Start#define STR_LEN 50typedef struct _person{int ssn; // 주민등록번호char name[STR_LEN]; // 이름char addr[STR_LEN]; // 주소} Person;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;}////////// Person End////////// Slot2 Starttypedef int Key;typedef Person * Value;typedef struct _slot{Key key;Value val;} Slot;////////// Slot2 End////////// DLinkedList Start#define TRUE 1#define FALSE 0// typedef int LData;typedef Slot LData;typedef struct _node{LData data;struct _node * next;} Node;typedef struct _linkedList{Node * head;Node * cur;Node * before;int numOfData;int (*comp)(LData d1, LData d2);} LinkedList;typedef LinkedList List;void ListInit(List * plist){plist->head = (Node*)malloc(sizeof(Node));plist->head->next = NULL;plist->comp = NULL;plist->numOfData = 0;}void FInsert(List * plist, LData data){Node * newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = plist->head->next;plist->head->next = newNode;(plist->numOfData)++;}void SInsert(List * plist, LData data){Node * newNode = (Node*)malloc(sizeof(Node));Node * pred = plist->head;newNode->data = data;while(pred->next != NULL &&plist->comp(data, pred->next->data) != 0){pred = pred->next;}newNode->next = pred->next;pred->next = newNode;(plist->numOfData)++;}void LInsert(List * plist, LData data){if(plist->comp == NULL)FInsert(plist, data);elseSInsert(plist, data);}int LFirst(List * plist, LData * pdata){if(plist->head->next == NULL)return FALSE;plist->before = plist->head;plist->cur = plist->head->next;*pdata = plist->cur->data;return TRUE;}int LNext(List * plist, LData * pdata){if(plist->cur->next == NULL)return FALSE;plist->before = plist->cur;plist->cur = plist->cur->next;*pdata = plist->cur->data;return TRUE;}LData LRemove(List * plist){Node * rpos = plist->cur;LData rdata = rpos->data;plist->before->next = plist->cur->next;plist->cur = plist->before;free(rpos);(plist->numOfData)--;return rdata;}int LCount(List * plist){return plist->numOfData;}void SetSortRule(List * plist, int (*comp)(LData d1, LData d2)){plist->comp = comp;}////////// DLinkedList End////////// Table2 Start#define MAX_TBL 100typedef int HashFunc(Key k);typedef struct _table{// Slot tbl[MAX_TBL];List 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);void TBLInit(Table * pt, HashFunc * f){int i;for(i=0; i<MAX_TBL; i++)ListInit(&(pt->tbl[i]));pt->hf = f;}void TBLInsert(Table * pt, Key k, Value v){int hv = pt->hf(k);Slot ns = {k, v};if(TBLSearch(pt, k) != NULL) // 키가 중복되었다면{printf("키 중복 오류 발생 \n");return;}else{LInsert(&(pt->tbl[hv]), ns);}}Value TBLDelete(Table * pt, Key k){int hv = pt->hf(k);Slot cSlot;if(LFirst(&(pt->tbl[hv]), &cSlot)){if(cSlot.key == k){LRemove(&(pt->tbl[hv]));return cSlot.val;}else{while(LNext(&(pt->tbl[hv]), &cSlot)){if(cSlot.key == k){LRemove(&(pt->tbl[hv]));return cSlot.val;}}}}return NULL;}Value TBLSearch(Table * pt, Key k){int hv = pt->hf(k);Slot cSlot;if(LFirst(&(pt->tbl[hv]), &cSlot)){if(cSlot.key == k){return cSlot.val;}else{while(LNext(&(pt->tbl[hv]), &cSlot)){if(cSlot.key == k)return cSlot.val;}}}return NULL;}////////// Table2 Endint MyHashFunc(int k){return k % 100;}int main(void){Table myTbl;Person * np;Person * sp;Person * rp;TBLInit(&myTbl, MyHashFunc);// 데이터 입력 ///////np = MakePersonData(900254, "Lee", "Seoul");TBLInsert(&myTbl, GetSSN(np), np);np = MakePersonData(900139, "KIM", "Jeju");TBLInsert(&myTbl, GetSSN(np), np);np = MakePersonData(900827, "HAN", "Kangwon");TBLInsert(&myTbl, GetSSN(np), np);// 데이터 탐색 ///////sp = TBLSearch(&myTbl, 900254);if(sp != NULL)ShowPerInfo(sp);sp = TBLSearch(&myTbl, 900139);if(sp != NULL)ShowPerInfo(sp);sp = TBLSearch(&myTbl, 900827);if(sp != NULL)ShowPerInfo(sp);// 데이터 삭제 ///////rp = TBLDelete(&myTbl, 900254);if(rp != NULL)free(rp);rp = TBLDelete(&myTbl, 900139);if(rp != NULL)free(rp);rp = TBLDelete(&myTbl, 900827);if(rp != NULL)free(rp);return 0;}