哈希表在查找方面有非常大應用價值,本文記錄一下利用哈希散列表來統計文本文件中每個單詞出現的重復次數,這個需求當然用NLP技術也很容易實現。
一、基本介紹
1、Hash Key值:將每個單詞按照字母組成通過一個乘子循環運算得出一個小於29989的整數,29989是一個比較大的質數。0~29989即為Key值。
2、哈希函數:

1 //哈希函數 2 unsigned int hashIndex(const char* pWord) //返回hash表的索引(即hash指針數組的下標) 3 { 4 assert(pWord != NULL); 5 unsigned int index = 0; //以下四行為將一個單詞映射到一個小於HASHNUMBER的正整數的函數 6 for (; *pWord != '\0'; pWord++) 7 index = MULT * index + *pWord; 8 return index % HASHNUMBER; 9 }
3、數據結構定義:
(1)總體采用數組法,數組下標就是Key值,Key取值范圍是1~29989,也即數組大小為29989,數組的每個項存儲該Key值下含有的單詞鏈表的頭指針,根據頭指針就能遍歷整個單詞鏈表
hashNodePtr bin[HASHNUMBER] = { NULL }; //HASHNUMBER大小的指針數組 作為hash表
(2)單詞節點定義: 鏈表存儲同一Key值下的單詞,單詞節點主要包含單詞內容、單詞的重復次數、指向下一個單詞的指針;
typedef struct hashnode { //鏈表中每個節點的結構 hashnode() { word = NULL; count = 0; next = NULL; } char* word; //單詞 int count; //出現頻率 struct hashnode *next; //指向鏈表中具有相同hash值的下個節點 }hashNode,*hashNodePtr;
4、哈希表解決沖突的途徑:鏈地址法。 即上面定義的存儲結構為鏈表。
二、源代碼
// case1.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 // #include "pch.h" #include <iostream> #include <assert.h> #define HASHNUMBER 29989 //散列表的大小,29989為質數 #define MULT 31 //hash函數的一個乘子
//單詞節點的定義 typedef struct hashnode { //鏈表中每個節點的結構 hashnode() { word = NULL; count = 0; next = NULL; } char* word; //單詞 int count; //出現頻率 struct hashnode *next; //指向鏈表中具有相同hash值的下個節點 }hashNode,*hashNodePtr; hashNodePtr bin[HASHNUMBER] = { NULL }; //HASHNUMBER大小的指針數組 作為hash表 //這里將每個單詞映射為一個小於HASHNUMBER的正整數 //哈希函數 unsigned int hashIndex(const char* pWord) //返回hash表的索引(即hash指針數組的下標) { assert(pWord != NULL); unsigned int index = 0; //以下四行為將一個單詞映射到一個小於HASHNUMBER的正整數的函數 for (; *pWord != '\0'; pWord++) index = MULT * index + *pWord; return index % HASHNUMBER; } //想hash表中插入單詞 void insertWord(const char* pWord) //在hash表中插入單詞,如果已經存在了,則增加單詞的出現次數count { assert(pWord != NULL); hashNodePtr p; unsigned int index = hashIndex(pWord); //用hash函數得到單詞的hash值,也就是hash數組的下標 for ( p = bin[index]; p !=NULL; p++) { //查找單詞是否已經在hash表中了 if (strcmp(pWord,p->word)==0) { //找到的話,直接將單詞的次數增加1即可 (p->count)++; return; } } //如果上面沒返回,也就是說hash表中沒有這個單詞,添加新節點,加入這個單詞 p = (hashNodePtr)malloc(sizeof(hashNode)); p->count = 1; //新節點的出現次數設置為1 p->word = (char *)malloc(strlen(pWord) + 1); strcpy(p->word, pWord); p->next = bin[index]; //將新生成的節點插入到index為下標的鏈表中去 bin[index] = p; } //讀取Data.txt中的單詞,並將每個單詞插入到前面設計好的hash表中 void readWordToHashTable(const char *path) { //從文本文件中讀取單詞,插入到hash表中 FILE *fp; char buf[1024]; //存儲一行字符串 char *p; fp = fopen(path, "r"); if (fp==NULL) { printf("open file error!exit\n"); exit(-1); } while (NULL!=fgets(buf,sizeof(buf),fp)) //數據讀完,到文本末尾了 { buf[strlen(buf) - 1] = '\0'; //出去單詞最后的換行符 //print("%s/n",buf); if (strcmp("",buf)==0) //如果是空行,則繼續 { continue; } p = strtok(buf, "'\t','\n',' '"); //用strtok函數從一行字符串中分離出每個單詞,分隔符設置為(空格、逗號、換行、制表符) while (p!=NULL) { insertWord(p); //調用insertWord(),向hash表中插入分隔出來的單詞 p = strtok(NULL, "'\t','\n'"); } } fclose(fp); } void writeHashTable(const char *path) {//將結果寫到path中。 FILE *fp; hashNodePtr p; int i; fp = fopen(path, "w"); if (fp == NULL) { printf("write file error!exit"); exit(-1); } for (i = 0; i < HASHNUMBER; i++) { for (p = bin[i]; p != NULL; p = p->next) { fprintf(fp, "index %d:<%s,%d>", i, p->word, p->count); if (p->next == NULL) fprintf(fp, "\n"); } } fclose(fp); } //釋放hash表中占用的內存 void freeHashTable() { int i; hashNodePtr p, q; p = q = NULL; for (i = 0; i < HASHNUMBER; i++) { p = bin[i]; while (p!=NULL) { q = p; p = p->next; free(q->word); free(q); } } } int main() { readWordToHashTable("data.txt"); writeHashTable("result.txt"); return 0; }
三、測試
由於這里無法上傳測試文件,請自己構造一個單詞文件,單詞與單詞之間的間隔只能是換行或者制表符,因為目前代碼中定義的區分單詞的間隔只有制表符和換行符,所以構造文件的時候,直接復制一篇英語作文進去,將其中的標點符號全部刪除,空格一律改成制表符,然后將該文本文件命名成data.txt,放入項目目錄下,運行程序,即可讀取該文件,並將統計結果的文件存儲在項目目錄下。
最后,附上筆者的實現項目源碼(包含data.txt測試文件):https://pan.baidu.com/s/17OVIuhf5tbaJ3TwsWzw-HA
參考鏈接:https://blog.csdn.net/shangshanhu/article/details/5917230