撰写这篇文章之前,先谈下个人对程序员编程素养的理解:

程序员除了数据结构与算法,什么也不属于自己---记得哪个NB人物曾经说过这样的话。的确,程序员水平高低如何,很大程度上取决于基本功是否扎实。高级程序员与普通码农的区别在我看来就是对这些基础知识是否做到了运用自如。许多程序员开发程序也是简单地调用已有系统库,或者第三方组件,写写简单的hello world或者if...else之类的程序,这样下去其个人竞争优势逐渐散失,很容易被新手赶超,究其原因在于平时没有对常见的基础知识打牢固,也没有对常见的技巧做总结,当然这里说的基础只是面向编程(可能还涉及到系统相关知识,暂且不论)所需的基础,包括语言语法知识,常见的数据结构算法技能等。也有不少程序员也仅仅停留在语言使用上面,至于为什么这么用不考究,语言贵在熟练而不在多,在我看来程序员掌握一种编译型语言(C/C++)与一种脚本语言(比如Shell或者Perl/Python/Php)就够了,在熟练掌握熟悉语言情况之下其他语言学起来成本也很低。就拿我们Linux C/C++编程来说,首先得熟练掌握C/C++编程语言语法知识,包括常见的语法及雷区,然后进一步则需要搞懂其内部实现原因,比如C++内存对象模型,虚函数实现原理。STL各类容器底层实现机制等,这样用起来也会因为其了解其原理而做到游刃有余。现实中,我们往往忽视这一点,常常因为一个小的细节问题搞得焦头烂额,追根溯源就是因为平时对理论基础不牢固导致。我们学习数据结构也是一样,知道常见的数据结构及其应用场景,才能在多变的问题中设计出合适的程序算法来。这就要我们平时训练过程中,不断提升个人的问题抽象能力,同时加深对操作系统,设计模式,网络编程等知识的理解,通过项目中遇到的问题难点来巩固基础。 既然说程序的灵魂就是数据结构加对应的算法,语言本身只是一门工具而已,那么就大可不必为了花大量时间在语言学习上了。我个人不是特别喜欢学习C++大量的语法特性,学习时间长,成本高,效率较低(不做嵌入式或者某些对性能要求较高的项目一般不用考虑用它了),当然毕竟与C语言语法相似,在平时也需要用C++来完成一些工具开发,掌握其基本语法还是有必要的。如今有不少好用的第三方库,比如boost、QT、ACE等,可以帮助我们高效开发,而不用自己造轮子了。不过,虽然个人工作大多数时候只用C语言这一门简洁的语言,但是语言并不是孤立的,需要结合操作系统,数据结构与算法等一起来综合考虑。平时自己用各类语言编程时,考虑到了每一行代码底层是如何运转的,语言帮我们实现了什么,需要我们做什么。实际上编程修炼的也就是自己对现实问题的抽象能力,通过对实际问题的剖析,抽象出好的数据结构,再用合理高效地算法去解决该问题。就拿个人来说,自己在这几年时间内也读过了不少计算机经典书籍,同时结合对项目的理解,也算是对基础的一个弥补吧。当然很高级的数据结构和算法现在没有涉及,比如什么B树,B+树,红黑树,图论相关的,动态规划或其他领域内的算法(KNN分类算法,谱聚类算法,朴素贝叶斯算法等)。毕竟我也只是一个应用程序开发者,不是搞基础算法研究的学者,比如大多数人只需了解C++ STL中某些容器的底层实现及它们的应用场景就够了,比如vector实际上就是一个动态数组; list,deque实现了其双向链表;map, multimap, set,multiset其底层实现利用了红黑树,该数据结构默认就是已经排序的等等。

下面具体回到hash链表的运用及实现上吧。为什么会出现hash这种数据结构呢,其实还是为了时间效率上的考虑,hash算法是一种典型的利用空间换取时间的算法,如果选取hash函数比较好,其hash值分布比较均匀,可以在O(1)时间内查找到所需的值。我们知道,数组具有随机存取功能,由于其空间在内存中连续分布,所以数组读取数值是相当快的;而链表这种数据结构,对于数据的增删有优势,但是对于访问某值的话,则需要从头结点找起,效率则有些低。而hash则是结合了这两者的优点。说的直观点就是将一大类数据用已知的hash函数进行分类,映射到固定大小的数组中,如果某一些数据不相同,但是通过映射之后,分到了同一类中(value值相同),则用链表来维护一组冲突域值。如果hash函数选取的好,则m个元素映射到n个slot(我们这里将数组index称之为槽)的平均查找时间为O(m/n)。可以这样说,hash算是分类的链表数组。hash具有查找效率高与去重功能,如果有此应用场景,考虑此数据结构。

先看一个简单的hash应用吧,统计单词词频:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define HASHLEN 2807303
typedef struct node
{char *word; //单词int count; //单词统计词频struct node *next;
} T_Node;T_Node *head[HASHLEN] = {NULL};// 最简单hash函数
int hash_function(char const *p)
{int value = 0;while (*p != '\0'){value = value * 31 + *p++;if (value > HASHLEN)value = value % HASHLEN;}return value;
}// 添加单词到hash表
void appendWord(char const *str)
{int index = hash_function(str);T_Node *pNode = head[index];while (pNode != NULL){if (strcmp(str, pNode->word) == 0) //找到对应的值,则词频加1,单词去重{(pNode->count)++;return;}pNode = pNode->next;}// 新建一个结点T_Node *q = (T_Node*)malloc(sizeof(T_Node));q->count = 1;q->word =  (char*)malloc(strlen(str)+1);strcpy(q->word, str);q->next = head[index];head[index] = q;
}int deleteWord(char const *str)
{int index = hash_function(str);T_Node *pHead = head[index];T_Node *preHead = NULL;while (pHead != NULL){if (strcmp(str, pHead->word) == 0){T_Node *deNode = pHead;pHead = pHead->next;if (NULL != preHead){preHead->next = pHead;}else{head[index] = pHead;}free(deNode->word);deNode->word = NULL;free(deNode);deNode = NULL;return 0;}else{preHead = pHead;pHead = pHead->next;}}return -1;
}void printWords() //打印hash表中的所有单词,及统计词频
{int i = 0;while (i < HASHLEN){for (T_Node *p = head[i]; p != NULL; p = p->next){fprintf(stdout, "%s:%d\n", p->word, p->count);}i++;}printf("**************\n");
}int main()
{char words[][16] = {"Remember", "what", "should", "be", "remembered", "and","forget", "what", "should", "be", "forgotten", "Alter", "what", "is","changeable", "and", "accept", "what", "is", "unchangeable"};for(unsigned i = 0; i < sizeof(words)/16; i++){appendWord(words[i]);}printWords();deleteWord("be");printWords();return 0;
}


通过上面简单的hash函数映射,我们可以很快地进行了单词词频统计及去重工作。在处理大数据时,考虑到性能,一般对10w以上数据存储可以考虑此类数据结构。

=================2016-03-27更新=========================

下面对算法进行合适的封装之后,更新代码如下:(参考了redis双向链表和项目hash代码数据结构封装)

//============================================================================
// Name        : hash算法演示程序
// Author      : @CodingGeek
// Version     : 1.0
// Time        : 2016-03-27
// Description :hash数据结构代码示例
//============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_WORD_LENGTH 32
typedef unsigned int UINT32;//定义维护的数据信息
typedef struct tagNode
{char word[MAX_WORD_LENGTH]; //单词UINT32 uiLength;//单词词频
}Node_S;//定义hash结点信息数据结构
typedef struct tagHash_Entry
{void *data; //数据域struct tagHash_Entry *pstNext; //指针域
} Hash_Entry_S;//定义冲突链表
typedef struct tagHash_List
{UINT32 uiLength; //链表长度Hash_Entry_S *pstEntry; //链表首结点
} Hash_List_S;//定义hash表
typedef struct tagHash_Table
{UINT32 (*pHashFunc)(void *, UINT32); //hash函数UINT32 (*pCmpFunc)(void*, void*, UINT32); //hash比较函数UINT32 uiHashSize; //hash bucket大小Hash_List_S *pstHashList; //hash表头指针
} Hash_Table_S;//下面定义hash散列的常见操作
UINT32 Hash_Function(void *pKey, UINT32 uiHashSize)
{if (NULL == pKey || 0 == uiHashSize){return -1;}UINT32 uiIndex = 0;char *pChar = (char*) pKey;while (*pChar != '\0'){uiIndex = uiIndex * 31 + *pChar++;if (uiIndex > uiHashSize){uiIndex %= uiHashSize;}}return uiIndex;
}UINT32 Hash_MatchFunc(void *pKey1, void *pKey2, UINT32 uiKeySize)
{if (memcmp(pKey1, pKey2, uiKeySize) == 0){return 1;}else{return 0;}
}//创建一个hash桶大小为uiHashSize的hash表,同时外界挂接回调函数来指定hash函数与比较函数Hash_Table_S *Hash_Create(UINT32 uiHashSize, UINT32 (*pHashFunc)(void*, UINT32),UINT32 (*pCmpFunc)(void*, void*, UINT32))
{Hash_Table_S *pstTable = NULL;pstTable = (Hash_Table_S*)malloc(sizeof(Hash_Table_S));if (NULL == pstTable){return NULL;}pstTable->pstHashList = (Hash_List_S*) malloc(sizeof(Hash_List_S) * uiHashSize);if (NULL == pstTable->pstHashList){free(pstTable);return NULL;}for (UINT32 uiLoop = 0; uiLoop < uiHashSize; uiLoop++){pstTable->pstHashList[uiLoop].pstEntry = NULL;pstTable->pstHashList[uiLoop].uiLength = 0;}//挂接外部指定的回调接口pstTable->pHashFunc = pHashFunc;pstTable->pCmpFunc = pCmpFunc;pstTable->uiHashSize = uiHashSize;return pstTable;
}//释放所有hash数据结点,清空hash数据结构
UINT32 Hash_Free(Hash_Table_S *pstTable)
{if (NULL == pstTable){return -1;}if (NULL == pstTable->pstHashList){free(pstTable);return 0;}for (UINT32 uiIndex = 0; uiIndex < pstTable->uiHashSize; uiIndex++){Hash_Entry_S *pstHead = pstTable->pstHashList[uiIndex].pstEntry;while (pstHead != NULL){Hash_Entry_S *pTemp = pstHead;pstHead = pstHead->pstNext;if (pTemp->data){free(pTemp->data);pTemp->data = NULL;}free(pTemp);}}free(pstTable->pstHashList);pstTable->pstHashList = NULL;free(pstTable);pstTable = NULL;return 0;
}//Hash查找,通过key来查找某个元素是否存在,存在返回列表元素,找不到返回NULL
Hash_Entry_S *Hash_Find_ByKey(Hash_Table_S *pstTable, void *pKey, UINT32 uiKeySize)
{if (NULL == pstTable || pKey == NULL || uiKeySize == 0){return NULL;}if (NULL == pstTable->pstHashList || pstTable->uiHashSize == 0){return NULL;}UINT32 uiIndex = pstTable->pHashFunc(pKey, pstTable->uiHashSize);Hash_Entry_S *pstEntry = pstTable->pstHashList[uiIndex].pstEntry;UINT32 uiLoop = 0;while (uiLoop < pstTable->pstHashList[uiIndex].uiLength && pstEntry){void *pEntryKey = pstEntry->data;if (pstTable->pCmpFunc(pKey, ((Node_S *)pEntryKey)->word, uiKeySize)){return pstEntry; //找到返回冲突链表中结点地址}uiLoop++;pstEntry = pstEntry->pstNext;}return NULL; //没有找到}//增加一个元素到hash表中
UINT32 Hash_Insert(Hash_Table_S *pstTable, void *pKey, UINT32 uiKeySize)
{if (NULL == pstTable || pKey == NULL || uiKeySize == 0){return -1;}if (NULL == pstTable->pstHashList || pstTable->uiHashSize == 0){return -1;}Hash_Entry_S *pstEntry = Hash_Find_ByKey(pstTable, pKey, uiKeySize);if (pstEntry) //如果找到了,更新数据域,比如词频数加1{((Node_S*)pstEntry->data)->uiLength++; //词频计数加1return 1;}//没有找到,申请新结点,挂接到该链上Hash_Entry_S *pstNew = (Hash_Entry_S*)malloc(sizeof(Hash_Entry_S));if (pstNew == NULL){return -1;}Node_S *pNode = (Node_S*)malloc(sizeof(Node_S));if (pNode == NULL){free(pstNew);return -1;}memset(pNode, 0x0, sizeof(Node_S));memcpy(pNode->word, pKey, uiKeySize);pNode->uiLength = 1;pstNew->data = pNode;UINT32 uiIndex = pstTable->pHashFunc(pKey, pstTable->uiHashSize);pstNew->pstNext = pstTable->pstHashList[uiIndex].pstEntry;pstTable->pstHashList[uiIndex].pstEntry = pstNew;pstTable->pstHashList[uiIndex].uiLength++; //长度加1return 0;}//hash表中删除一个元素
UINT32 Hash_Delete(Hash_Table_S *pstTable, void *pKey, UINT32 uiKeySize)
{if (NULL == pstTable || pKey == NULL || uiKeySize == 0){return -1;}if (NULL == pstTable->pstHashList || pstTable->uiHashSize == 0){return -1;}UINT32 uiIndex = pstTable->pHashFunc(pKey, pstTable->uiHashSize);Hash_Entry_S *pHead = pstTable->pstHashList[uiIndex].pstEntry;Hash_Entry_S *preHead = NULL;while (pHead != NULL){void *pEntryKey = pHead->data;//比较key根据实际情况来确定if (pstTable->pCmpFunc(pKey, ((Node_S*)pEntryKey)->word, uiKeySize)){Hash_Entry_S *delEntry = pHead;pHead = pHead->pstNext;if (NULL != preHead){preHead->pstNext = pHead;}else{pstTable->pstHashList[uiIndex].pstEntry = pHead;}if (delEntry->data){free(delEntry->data);delEntry->data = NULL;}free(delEntry);delEntry = NULL;pstTable->pstHashList[uiIndex].uiLength--;return 0;}else{preHead = pHead;pHead = pHead->pstNext;}}return -1;
}//hash遍历所有结点
void Hash_Traverse(Hash_Table_S *pstTable)
{UINT32 uiIndex = 0;Hash_Entry_S *pstEntry = NULL;if (NULL == pstTable){return;}if (NULL == pstTable->pstHashList){return;}for (uiIndex = 0; uiIndex < pstTable->uiHashSize; uiIndex++){pstEntry = pstTable->pstHashList[uiIndex].pstEntry;while (NULL != pstEntry){printf("%s:%d->",((Node_S*)pstEntry->data)->word,((Node_S*)pstEntry->data)->uiLength);pstEntry = pstEntry->pstNext;}printf("NULL\n");}printf("=========END============\n");}int main(void)
{Hash_Table_S *pstTable = Hash_Create(15, Hash_Function, Hash_MatchFunc);char *str[] = {"hello", "world", "I", "Like", "Programming", "world", "this", "is","a", "test", "programming", "demo", "you", "should","write","more","and", "more","test","cases","you","want"};for (UINT32 uiLoop = 0; uiLoop < (sizeof(str)/sizeof(str[0])); uiLoop++){Hash_Insert(pstTable, str[uiLoop], strlen(str[uiLoop]));}Hash_Traverse(pstTable);Hash_Delete(pstTable, (void*)"more", strlen("more"));Hash_Traverse(pstTable);Hash_Free(pstTable);pstTable = NULL;return 0;
}

打印结果如下:

more:2->write:1->Programming:1->NULL
demo:1->NULL
cases:1->programming:1->NULL
NULL
you:2->NULL
this:1->NULL
NULL
want:1->and:1->a:1->hello:1->NULL
NULL
NULL
should:1->is:1->NULL
NULL
world:2->NULL
test:2->I:1->NULL
Like:1->NULL
=========END============
write:1->Programming:1->NULL
demo:1->NULL
cases:1->programming:1->NULL
NULL
you:2->NULL
this:1->NULL
NULL
want:1->and:1->a:1->hello:1->NULL
NULL
NULL
should:1->is:1->NULL
NULL
world:2->NULL
test:2->I:1->NULL
Like:1->NULL
=========END============

Hash函数经典用法相关推荐

  1. PHP 可变函数经典用法

    <?phpfunction map($fun, $list,$params=array()){$acc=NULL;$last=array_push($params, NULL,$acc)-1;f ...

  2. mysql 字符串的hash函数_经典字符串Hash函数介绍 - yanjun_1982的专栏 - CSDNBlog

    作者阅读过大量经典软件原代码,下面分别介绍几个经典软件中出现的字符串Hash函数. 2.1 PHP中出现的字符串Hash函数 static unsigned long hashpjw(char *ar ...

  3. SUMPRODUCT函数的经典用法

    SUMPRODUCT函数是excel07版本后新增的一个函数,功能多样且强大,是excel中的一颗璀璨明星,今天我们来看一下这个函数的用法. 语法:=SUMPRODUCT(array1,array2, ...

  4. Hash 函数资源链接汇总

    Hash 链接: [1]General Purpose Hash Function Algorithms:http://www.partow.net/programming/hashfunctions ...

  5. php session举例,PHP 中session的经典用法

    PHP中session的经典用法 PHP中的session默认情况下是使用客户端的Cookie.当客户端的Cookie被禁用时,会自动通过Query_String来传递. Php处理session会话 ...

  6. 认知理论与技术(hash函数,SHA,MD

    概述: 认证是个过程,通过这个过程一个实体向另一个实体证明某种声称的属性 认证参数: "口令"相当于平时登录系统时输入的密码. "密钥"相当于公钥密码体制中的私 ...

  7. MYSQL 加密函数的用法

    常见加密函数有md5,hash,password等,mysql应用web一般都是md5,32位的,下面讲将MYSQL 加密函数的用法.1,md5加密用法select md5('admin');2,pa ...

  8. hash函数查找和ASL计算

    Hash表的"查找成功的ASL"和"查找不成功的ASL" ASL指的是 平均查找时间 关键字序列:(7.8.30.11.18.9.14) 散列函数:  H(Ke ...

  9. 带你自学Python系列(十一):Python函数的用法(一)

    ↑ 点击上方[计算机视觉联盟]关注我们 今天是小编持续更新关于Python的知识总结以及Python实践项目应用的第11天,带你利用零碎时间自学最受欢迎的编程语言之一Python语言.你和小编一起打卡 ...

最新文章

  1. java查看对象锁级别_对象级别锁 vs 类级别锁(Java)
  2. 旋转卡壳——模板(对踵点)
  3. 利用编码特长,我赚取了每月1000美元的额外收入
  4. java获取当前方法
  5. 循环获取枚举值和名称
  6. 智能合约重构社会契约 (3)智能合约的自动触发
  7. AIX忘记root密码后,重设密码步骤
  8. mysql 创建表check如何使用_MySQL怎么使用check约束
  9. 敏捷开发绩效管理之六:敏捷开发生产率(中)(功能点分析,FPA,简化的功能点)...
  10. js设计模式之观察者模式和发布/订阅模式
  11. 基于多线程的Linux聊天室系统设计(C语言实现)
  12. python用什么来写模块-史上最详细的python模块讲解
  13. 【Ubuntu】在Ubuntu 12.04 LTS上安装JDK6
  14. 模糊综合评价模型详解
  15. HUAWEI HiCar让华为手机用户中的宝马车主Hi起来!
  16. 一文带你了解隐私 Layer1
  17. 鸿蒙天钟小白图片,果然又一令人震惊的取名方式-“小白”
  18. strstr函数和strtok函数的使用
  19. J-Link 识别设备失败----NRF52832
  20. python基础: os.path.realpath()、os.path.getcwd()、 os.path.abspath() 的区别

热门文章

  1. 苹果怎么把某个app隐藏_怎么给苹果手机APP加密?
  2. 高通MSM8998芯片数据资料参考
  3. 家庭光纤宽带延长光纤
  4. 查找SCI期刊的影响因子
  5. 基于深度学习下的稳定学习究竟是什么?因果学习?迁移学习?之一
  6. IOS 跳转导航地图
  7. 中医知识分享之《养生十八伤》
  8. 抓取全网财经新闻,计算新闻相关股票的多空舆情,量化买入
  9. educoder平台哪里有答案_2020青骄第二课堂登陆平台地址入口 青骄第二课堂禁毒知识竞赛入口...
  10. 如何给刚刚出厂的服务器配置IP地址(华为RH2288 v3)