スポンサーリンク

ハッシュテーブルを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

スポンサーリンク