Hash函数经典用法

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

        程序员除了数据结构与算法,什么也不属于自己---记得哪个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 
#include 
#include 

#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函数经典用法_第1张图片

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

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

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

//============================================================================
// Name        : hash算法演示程序
// Author      : @CodingGeek
// Version     : 1.0
// Time        : 2016-03-27
// Description :hash数据结构代码示例
//============================================================================
#include 
#include 
#include 
#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++; //词频计数加1
        return 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++; //长度加1
    return 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============



你可能感兴趣的:(数据结构与算法,C语言)