旭说数据结构之散列表(哈希表)

对于线性表这种数据结构,当我们查找指定值的时候,只能通过与表中各个元素进行比较的方法。因为线性表每个位置上存放的值与位置没有关系,这个位置上想放什么值就放什么值。
而散列表(又称哈希表)这种数据结构每个位置上放置的数据的值与位置是有关系的,换句话说,你的数值是A,就只能放到 f(A) 这个位置上,你的数值是B,就只能放到 f(B) 上,这样当我想要知道数值A在不在散列表中的时候,我就计算一下f(A),然后看看f(A)这个位置的值是不是A,如果是A就说明我们找到了散列表中的A。这里的 f() 是元素值与存储位置的对应关系。
这里写图片描述

不难想到, f(x) 是构建散列表的关键,不同的 f(x) 会把相同的元素值映射到不同的存储位置。我们称 f(x) 为散列函数。
如何设计 f(x) 呢?最好就是我给了你一堆 n 个数字,和n个空间,你能把每个数字通过 f(x) 计算后,给它找到一个坑安置它,不会出现两个数字争夺一个坑的情况。
常用的散列函数构造方法有:

  1. 直接定址法
    把要存放的数值(关键字)作为地址或者是该数值的线性函数作为地址。
    f(x)=ax+b.(ab)
    一般用于事先知道了关键字集合,且关键字集合不大、连续性好。
  2. 除留余数法
    就是把要存放的数值除以某个数,把得到的余数作为地址。
    f(x)=xmodp;(p)
    对p的选择很重要,一般取接近表容量的最小素数。
  3. 平方取中法
    把关键字平方,取平方结果的中间几位作为地址。
    比如,给你一个数值 111 ,平方后得到 12321 ,如果取中间 1 位作为地址,则3 111 的散列地址。
    再给个数值 222 ,平方后得到 49284 ,如果取中间 1 位作为地址,则2 111 的散列地址。
    想要完美的构造散列函数是不可能的,因此总会出现几个数值抢坑的局面。比如:
    这里写图片描述
    常用的处理抢坑(冲突)问题的方法有:
    1. 开放定址法。比如上图中,我计算出 24 的地址为 0 ,然后我到位置 0 处发现位置0处存放的是 100 ,这时我就不能把 24 放在位置 0 处,我转而寻找下一个空白的散列地址。我先计算(f(24)+d1)modn(其中 n 是散列表的长度,d1是序列 d1,d2,d3...dn1 中的第一个值),看看计算所得的地址处有没有值,如果没有就把24存进去,如果有值,再计算 (f(24)+d2)modn ,如此这般,直到找到的地址处没有值把 24 插入。
      序列一般有三种的取值方式,
      • 线性探测再散列, d=1,2...n1
      • 二次探测再散列, d=12,12,22,22...v2,v2(vn/2)
    2. 链地址法
      将所有具有相同散列地址的不同元素值放到一个单链表中,在散列表中存放这些单链表的头指针。
    3. 建立一个公共溢出区

下面是一个利用开放地址法写的例子:

template<typename DataType>class HashTable
{
public:
    HashTable(int capacity = 10)
    {
        _capacity = capacity;
        _datas = new DataType[capacity];
        _length = 0;
        for (int i = 0;i<capacity;i++)
        {
            _datas[i]=NULL;
        }
    }
    ~HashTable()
    {
        delete [] _datas;
    }
    //采用除留余数法f(x)=data%p
    int hashFunc(DataType data,int p)
    {
        return data % p;
    }
    //返回要查找元素的地址,利用开放定址法来解决冲突
    int searchData(DataType data)
    {
        int place = hashFunc(data,11);
        if (_datas[place] == data)//如果该地址处的值等于我要找的值
        {
            return place;
        }
        //如果该地址的值不等于我要找的值,利用开放地址法
        int nextPlace = (place + 1)%_capacity;
        while(nextPlace != place){
            if (_datas[nextPlace] == data)//如果这个地方的值等于我们要找的data了
            {
                return nextPlace;
            }
            if (_datas[nextPlace] == NULL)//如果这个地方还没有值,那就说明我们要找的data不在里面
            {
                break;
            }
            nextPlace = (nextPlace +1)%_capacity;
        }
        if (nextPlace == place)//如果循环了一圈,既没有找到空白地址,也没有找到存有data值的地址,则说明这个值不在该散列表中,同时也没有地方给这个值用了
        {
            return -1;
        }else{//找到了空白的区域

            cout<<"你要的值不在散列表中,我找到了一个空白区域,你可以把值插入进去了"<<endl;
            return nextPlace;
        }

    }
    bool insertData(DataType data)
    {
        int place = searchData(data);
        if (place < 0)  return false;
        //如果要插入的值已经在散列表中,返回false
        if (_datas[place] == data)
        {
            return false;
        }
        //能到这一步,说明searchData返回的是一个空白区域
        _datas[place]=data;
        return true;
    }

    void printDatas(){
        for (int i =0;i<_capacity;i++)
        {
            cout<<_datas[i]<<endl;
        }
    }
private:
    int _capacity;
    int _length;
    DataType * _datas;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值