スポンサーリンク
ハッシュテーブルをc言語で実装
今回は下記のようなハッシュ関数を採用することにしました。
とてもシンプルです。
キーとなる入力文字列の各文字のアスキーコードの和を求めて、ハッシュテーブルのテーブルサイズで割った余りをハッシュ値としました。
int hashFunc(char* keyStr){ int sum = 0; int i = 0; while(keyStr[i] != '\0'){ sum += keyStr[i]; i++; } return sum % SIZE_TABLE; }
今回はキーの衝突(異なるキーでもハッシュ値が同じになる場合にがある)を前提としています。
衝突した場合は、同じハッシュ値となったキー同士を連結リストを使って格納していきます。
参考:連結リスト(片方向)でpushとpopをc言語で実装(サンプルコードあり)
typedef struct node{ char keyStr[SIZE_KEYSTR]; int value; struct node* pNextNode; }node_t;
そして、ハッシュテーブルには連結リストの先頭のポインタを格納します。
下記のように宣言します。
int main(){ node_t* pHashTable[SIZE_TABLE]; //省略 }
それでは、ハッシュテーブルにキーを登録する関数です。
大まかに下記のステップで関数を実装しています。
1. キーのハッシュ値を求める
2. ハッシュ値を、ハッシュテーブルの配列のインデックスとする
3. ハッシュ値に紐付く連結リストへのポインタを取得する
※ハッシュ値で指定した要素は、連結リストの先頭のポインタとなっている
4. 連結リストを検索して、入力キーが存在するか判定
存在しない→値を書き換え
存在した →新規でノードを登録
node_t* initNode(char* keyStr, int value){ node_t* pNode = NULL; pNode = (node_t*)malloc(sizeof(node_t)); if(pNode == NULL){ printf("init malloc error\n"); return NULL; } strcpy(pNode->keyStr, keyStr); pNode->value = value; pNode->pNextNode = NULL; return pNode; } void registerKeyVal(node_t** pHashTable, char* keyStr, int value){ node_t* pNode = NULL; node_t* pPreNode = NULL; if(strlen(keyStr) > SIZE_KEYSTR - 1){ printf("arg error\n"); return; } //連結リストが存在しない場合 if(pHashTable[hashFunc(keyStr)] == NULL){ pHashTable[hashFunc(keyStr)] = initNode(keyStr, value); if(pHashTable[hashFunc(keyStr)] == NULL){ return; } printf("Registered\n"); printfHashTable(pHashTable); return; } //連結リストが存在した場合 pNode = pHashTable[hashFunc(keyStr)]; pPreNode = pNode; while(pNode != NULL){ if(strcmp(keyStr, pNode->keyStr) == 0){ pNode->value = value; printf("Reregistered\n"); printfHashTable(pHashTable); return; } pPreNode = pNode; pNode = pNode->pNextNode; } pPreNode->pNextNode = initNode(keyStr, value); //error chkはなし printf("Registered\n"); printfHashTable(pHashTable); return; }
続いて、キーを削除する関数です。
ハッシュテーブルに紐付く連結リストに、同じキーが存在するか検索します。
存在すればfreeして、freeした左右のノードを連結し直します。
void deleteKey(node_t** pHashTable, char* keyStr){ node_t* pNode = NULL; node_t* pPreNode = NULL; if(pHashTable[hashFunc(keyStr)] == NULL){ printf("No key\n"); printfHashTable(pHashTable); return; } pNode = pHashTable[hashFunc(keyStr)]; while(pNode != NULL){ if(strcmp(keyStr, pNode->keyStr) == 0){ //連結リストの先頭のノードの処理 if(pNode == pHashTable[hashFunc(keyStr)]){ pHashTable[hashFunc(keyStr)] = pNode->pNextNode; //連結リストの2ノード目以降の場合は、連結リストを連結し直す }else{ pPreNode->pNextNode = pNode->pNextNode; } free(pNode); printf("Delete key\n"); printfHashTable(pHashTable); return; } pPreNode = pNode; pNode = pNode->pNextNode; } printf("No key\n"); printfHashTable(pHashTable); return; }
pPreNodeの役割についていくつかご質問を頂いていたので、
この場で回答させて頂きます。
while(pNode != NULL){ 〜省略〜 pPreNode->pNextNode = pNode->pNextNode; } 〜省略〜 } pPreNode = pNode; pNode = pNode->pNextNode; }
理由としては、pNodeの中身を書き換えたあとに、書き換える前のpNodeの値を参照するからです。
なので、一旦pNodeの中身を書き換える前に、pPreNodeに退避させておきます。
そうすることで、pNodeの中身を書き換えたあともpPreNodeを使えば、書き換える前のpNodeを参照することができます。
・一旦pNodeの中身を書き換える前に、pPreNodeに退避させておきます。
pPreNode->pNextNode = pNode->pNextNode;
・pNodeの値を書き換えます。
pNode = pNode->pNextNode
pPreNodeを参照することで、書き換える前のpNodeの値を参照することができる。
pPreNode->pNextNode = pNode->pNextNode;
スポンサーリンク
サンプルコード
下記がサンプルコードになります。
$ cat sample.c #include <stdio.h> #include <stdlib.h> #include <string.h> #define SIZE_KEYSTR 3 #define SIZE_TABLE 3 typedef struct node{ char keyStr[SIZE_KEYSTR]; int value; struct node* pNextNode; }node_t; void printfHashTable(node_t** pHashTable){ node_t* pNode = NULL; int i; for(i = 0; i < SIZE_TABLE; i++){ if(pHashTable[i] != NULL){ pNode = pHashTable[i]; while(pNode != NULL){ printf("hashVal:%d key:%s value:%d\n", i, pNode->keyStr, pNode->value); pNode = pNode->pNextNode; } } } printf("\n"); return; } int hashFunc(char* keyStr){ int sum = 0; int i = 0; while(keyStr[i] != '\0'){ sum += keyStr[i]; i++; } return sum % SIZE_TABLE; } void initHashTable(node_t** pHashTable){ int i; for(i = 0; i < SIZE_TABLE; i++){ pHashTable[i] = NULL; } printfHashTable(pHashTable); } node_t* initNode(char* keyStr, int value){ node_t* pNode = NULL; pNode = (node_t*)malloc(sizeof(node_t)); if(pNode == NULL){ printf("init malloc error\n"); return NULL; } strcpy(pNode->keyStr, keyStr); pNode->value = value; pNode->pNextNode = NULL; return pNode; } void registerKeyVal(node_t** pHashTable, char* keyStr, int value){ node_t* pNode = NULL; node_t* pPreNode = NULL; if(strlen(keyStr) > SIZE_KEYSTR - 1){ printf("arg error\n"); return; } //連結リストが存在しない場合 if(pHashTable[hashFunc(keyStr)] == NULL){ pHashTable[hashFunc(keyStr)] = initNode(keyStr, value); if(pHashTable[hashFunc(keyStr)] == NULL){ return; } printf("Registered\n"); printfHashTable(pHashTable); return; } //連結リストが存在した場合 pNode = pHashTable[hashFunc(keyStr)]; pPreNode = pNode; while(pNode != NULL){ if(strcmp(keyStr, pNode->keyStr) == 0){ pNode->value = value; printf("Reregistered\n"); printfHashTable(pHashTable); return; } pPreNode = pNode; pNode = pNode->pNextNode; } pPreNode->pNextNode = initNode(keyStr, value); //error chkはなし printf("Registered\n"); printfHashTable(pHashTable); return; } void deleteKey(node_t** pHashTable, char* keyStr){ node_t* pNode = NULL; node_t* pPreNode = NULL; if(pHashTable[hashFunc(keyStr)] == NULL){ printf("No key\n"); printfHashTable(pHashTable); return; } pNode = pHashTable[hashFunc(keyStr)]; while(pNode != NULL){ if(strcmp(keyStr, pNode->keyStr) == 0){ //連結リストの先頭のノードの処理 if(pNode == pHashTable[hashFunc(keyStr)]){ pHashTable[hashFunc(keyStr)] = pNode->pNextNode; //連結リストの2ノード目以降の場合は、連結リストを連結し直す }else{ pPreNode->pNextNode = pNode->pNextNode; } free(pNode); printf("Delete key\n"); printfHashTable(pHashTable); return; } pPreNode = pNode; pNode = pNode->pNextNode; } printf("No key\n"); printfHashTable(pHashTable); return; } void deleteNode(node_t** pHashTable){ node_t* pNode = NULL; node_t* pPreNode = NULL; int i; for(i = 0; i < SIZE_TABLE; i++){ if(pHashTable[i] != NULL){ pNode = pHashTable[i]; while(pNode != NULL){ pPreNode = pNode; pNode = pNode->pNextNode; free(pPreNode); } pHashTable[i] = NULL; } } printf("Delete all the node\n"); printfHashTable(pHashTable); return; } int main(){ node_t* pHashTable[SIZE_TABLE]; initHashTable(pHashTable); registerKeyVal(pHashTable, "c", 3); registerKeyVal(pHashTable, "d", 4); registerKeyVal(pHashTable, "e", 5); registerKeyVal(pHashTable, "f", 6); registerKeyVal(pHashTable, "d", 1); deleteKey(pHashTable, "c"); deleteKey(pHashTable, "c"); deleteKey(pHashTable, "f"); deleteNode(pHashTable); registerKeyVal(pHashTable, "c", 3); deleteNode(pHashTable); return 0; }
下記が実行結果になります。
$ gcc sample.c -o sample $ ./sample Registered hashVal:0 key:c value:3 Registered hashVal:0 key:c value:3 hashVal:1 key:d value:4 Registered hashVal:0 key:c value:3 hashVal:1 key:d value:4 hashVal:2 key:e value:5 Registered hashVal:0 key:c value:3 hashVal:0 key:f value:6 hashVal:1 key:d value:4 hashVal:2 key:e value:5 Reregistered hashVal:0 key:c value:3 hashVal:0 key:f value:6 hashVal:1 key:d value:1 hashVal:2 key:e value:5 Delete key hashVal:0 key:f value:6 hashVal:1 key:d value:1 hashVal:2 key:e value:5 No key hashVal:0 key:f value:6 hashVal:1 key:d value:1 hashVal:2 key:e value:5 Delete key hashVal:1 key:d value:1 hashVal:2 key:e value:5 Delete all the node Registered hashVal:0 key:c value:3 Delete all the node
スポンサーリンク