这一篇要总结的是五天查找的最后一篇,哈希查找,也称为散列查找(本文以哈希称呼)。提起哈希,我的第一印象就是C#中的Hashtable类,它是由一组key/value的键值对组成的集合,它就是应用了散列技术。

那么,什么是哈希查找呢?在弄清楚什么是哈希查找之前,我们要弄清楚哈希技术,哈希技术是在记录的存储位置和记录的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。哈希技术既是一种存储方法,也是一种查找方法。

六种哈希函数的构造方法:

1,直接定址法:

函数公式:f(key)=a*key+b (a,b为常数)

这种方法的优点是:简单,均匀,不会产生冲突。但是需要事先知道关键字的分布情况,适合查找表较小并且连续的情况。

2,数字分析法:

比如我们的11位手机号码“136XXXX7887”,其中前三位是接入号,一般对应不同运营公司的子品牌,如130是联通如意通,136是移动神州行,153是电信等。中间四们是HLR识别号,表示用户归属地。最后四们才是真正的用户号。

若我们现在要存储某家公司员工登记表,如果用手机号码作为关键字,那么极有可能前7位都是相同的,所以我们选择后面的四们作为哈希地址就是不错的选择。

3,平方取中法:

故名思义,比如关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227作为哈希地址。

4,折叠法:

折叠法是将关键字从左到右分割成位数相等的几个部分(最后一部分位数不够可以短些),然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址。

比如我们的关键字是9876543210,哈希表表长三位,我们将它分为四组,987|654|321|0 ,然后将它们叠加求和987+654+321+0=1962,再求后3位即得到哈希地址为962,哈哈,是不是很有意思。

5,除留余数法:

函数公式:f(key)=key mod p (p<=m)m为哈希表表长。

这种方法是最常用的哈希函数构造方法。

6,随机数法:

函数公式:f(key)= random(key)。

这里random是随机函数,当关键字的长度不等是,采用这种方法比较合适。

两种哈希函数冲突解决方法:

我们设计得最好的哈希函数也不可能完全避免冲突,当我们在使用哈希函数后发现两个关键字key1!=key2,但是却有f(key1)=f(key2),即发生冲突。

方法一:开放定址法:

开放定址法就是一旦发生了冲突,就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总是能找到,然后将记录插入。这种方法是最常用的解决冲突的方法。

方法二:链地址法:

下面是实现代码:

C#版本:

namespace HashSearch.CSharp
{
    class Program
    {
        //初始化哈希表
        static int hashLength = 7;
        static int[] hashTable= new int[hashLength];

        //原始数据
        static List<int> list = new List<int>() { 13,29,27,28,26,30,38 };

        static void Main(string[] args)
        {
            Console.WriteLine("********************哈希查找(C#版)********************\n");
            
            //创建哈希表
            for (int i = 0; i < list.Count; i++)
            {
                Insert(hashTable,list[i]);
            }
            Console.WriteLine("展示哈希表中的数据:{0}",String.Join(",",hashTable));

            while (true)
            {
                //哈希表查找
                Console.Write("请输入要查找的数据:");
                int data = int.Parse(Console.ReadLine());
                var result = Search(hashTable, data);
                if (result == -1) Console.WriteLine("对不起,没有找到!");
                else Console.WriteLine("数据的位置是:{0}", result);
            }
        }

        /// <summary>
        /// 哈希表插入
        /// </summary>
        /// <param name="hashTable">哈希表</param>
        /// <param name="data">待插入值</param>
        public static void Insert(int[] hashTable, int data)
        { 
            //哈希函数,除留余数法
            int hashAddress = Hash(hashTable,data);

            //如果不为0,则说明发生冲突
            while (hashTable[hashAddress] != 0)
            {
                //利用开放定址的线性探测法解决冲突
                hashAddress = (++hashAddress) % hashTable.Length;
            }

            //将待插入值存入字典中
            hashTable[hashAddress] = data;
        }

        /// <summary>
        /// 哈希表查找
        /// </summary>
        /// <param name="hashTable">哈希表</param>
        /// <param name="data">待查找的值</param>
        /// <returns></returns>
        public static int Search(int[] hashTable, int data)
        {
            //哈希函数,除留余数法
            int hashAddress = Hash(hashTable,data);

            //冲突发生
            while (hashTable[hashAddress] != data)
            {
                //利用开放定址的线性探测法解决冲突
                hashAddress = (++hashAddress) % hashTable.Length;

                //查找到了开放单元或者循环回到原点,表示查找失败
                if (hashTable[hashAddress] == 0 || hashAddress==Hash(hashTable,data)) return -1;
            }
            //查找成功,返回值的下标
            return hashAddress;
        }

        /// <summary>
        /// 哈希函数(除留余数法)
        /// </summary>
        /// <param name="hashTable">待操作哈希表</param>
        /// <param name="data"></param>
        /// <returns>返回数据的位置</returns>
        public static int Hash(int[] hashTable, int data)
        {
            return data % hashTable.Length;
        }
    }
}

程序输出结果如图:

ds41

 

C语言版:

#include "stdio.h"    
#include "stdlib.h"   
#include "io.h"  
#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define SUCCESS 1
#define UNSUCCESS 0

#define HASHSIZE 7 /* 定义散列表长为数组的长度 */
#define NULLKEY -32768 

typedef int Status;    
typedef struct
{
    int *elem; /* 数据元素存储地址,动态分配数组 */
    int count; /*  当前数据元素个数 */
}HashTable;

int m=0; /* 散列表表长,全局变量 */

/*初始化*/
Status Init(HashTable *hashTable)
{
    int i;

    m=HASHSIZE;
    hashTable->elem= (int *)malloc(m*sizeof(int)); //申请内存
    hashTable->count=m;
    for (i=0;i<m;i++)
    {
        hashTable->elem[i]=NULLKEY;
    }
    return OK;
}

/*哈希函数(除留余数法)*/
int Hash(int data)
{
    return data%m;
}

/*插入*/
void Insert(HashTable *hashTable,int data)
{
    int hashAddress=Hash(data); //求哈希地址

    //发生冲突
    while(hashTable->elem[hashAddress]!=NULLKEY)
    {
        //利用开放定址的线性探测法解决冲突
        hashAddress=(++hashAddress)%m;
    }

    //插入值
    hashTable->elem[hashAddress]=data;
}

/*查找*/
int Search(HashTable *hashTable,int data)
{
    int hashAddress=Hash(data); //求哈希地址

    //发生冲突
    while(hashTable->elem[hashAddress]!=data)
    {
        //利用开放定址的线性探测法解决冲突
        hashAddress=(++hashAddress)%m;

        if (hashTable->elem[hashAddress]==NULLKEY||hashAddress==Hash(data)) return -1;
    }

    //查找成功
    return hashAddress;
}

/*打印结果*/
void Display(HashTable *hashTable)
{
    int i;
    printf("\n**********展示结果**********\n");

    for (i=0;i<hashTable->count;i++)
    {
        printf("%d ",hashTable->elem[i]);
    }

    printf("\n**********展示完毕**********\n");
}

void main()
{
    int i,j,result;
    HashTable hashTable;
    int arr[HASHSIZE]={13,29,27,28,26,30,38};

    printf("***************哈希查找(C语言版)***************\n");

    //初始化哈希表
    Init(&hashTable);

    //插入数据
    for (i=0;i<HASHSIZE;i++)
    {
        Insert(&hashTable,arr[i]);
    }
    Display(&hashTable);

    //查找数据
    result= Search(&hashTable,29);
    if (result==-1) printf("对不起,没有找到!");
    else printf("29在哈希表中的位置是:%d",result);

    getchar();
}

程序输出结果如图:

ds42

posted on 2013-09-01 17:29  永远的麦子  阅读(14403)  评论(1编辑  收藏  举报