数据结构(五)散列表

散列表–c++实现

散列表描述

理想散列

字典的另一种表示方法——散列(hashing)。它用一个散列函数(也称哈希函数)把字典的键值对映射到一个散列表(也称哈希表)的具体位置。

例如:对于字典元素p<k,v>,若散列函数为f,那么在理想状况下p在散列表中的位置为f(k)

可用理想散列的情况:需记录的字典元素中的关键字范围与元素个数相差不大。

例:有100个字典元素,它们的关键字范围是1200到1300,则可以使用函数f(k) = k - 1200来把字典元素映射到散列表的位置0到100之间

不可用理想散列的情况:需记录的字典元素中的关键字范围远大于元素个数。

例:有100个字典元素,它们的关键字范围从10000到99999,则使用函数f(k) = k - 10000,而f 的值域为[0,89999],那么散列表需要有90000个位置,但其中只存放了100个字典元素,显然使用如此长的散列表是不明智的,不仅浪费空间,而且要把90000个元素初始化为null也需要很长时间。


散列函数和散列表

1. 桶和起始桶

当关键字的范围太大,不能用理想方法表示时,可以采用并不理想的散列表和散列函数:散列表位置的数量比关键字的个数少,散列函数把若干个不同的关键字映射到散列表的同一个位置。散列表的每一个位置叫做一个;对关键字为k的键值对,f(k)起始桶 ;桶的数量等于散列表的长度或大小。因为散列函数可以把若干个关键字映射到同一个桶,所以桶要能够容纳多个键值对。此处仅考虑两种极端情景,第一种情况是每一个桶只能储存一个键值对,第二种情况就是每一个桶都是一个可以容纳全部键值对的线性表。

2. 除法散列函数

在多种散列函数中,最常用的是除法散列函数,它的形式如下:

f(k) = k % D

其中k是关键字,D是散列表的长度(即桶的数量),%为求模运算符。散列表的位置索引从0到D - 1

3. 冲突和溢出

假设一个散列表有11个桶,序号从0到10,而且每一个桶可以存储一个键值对,使用除法散列函数,对于关键字为40,58的两个键值对,但通过散列函数计算的散列表位置都是3,这个时候就发生了冲突。当两个不同的关键字所对应的起始桶相同时,就是冲突发生了。

因为一个桶可以存储多个键值对,因此发生冲突也没什么。只要起始桶足够大,所有对应同一个起始桶的键值对都可存储在一起。如果存储桶没有空间存储一个新键值对,就是溢出发生了。

在我们的假设中,每个桶只能存储一个键值对,因此冲突和溢出同时发生,若关键字为40的键值对已在散列表索引为3的位置处,那么关键字为58的键值对无法放入起始桶。这类问题由溢出处理方法来解决。最常用的方法是线性探查法(待会讨论)。

4. 好的散列函数

单论冲突而言不可怕,可怕的是它会带来溢出,除非一个桶可以容纳无限多个数对,否则插入时的溢出就难以处理。当映射到散列表中任何一个桶里的关键字数量大致相等时,冲突和溢出的平均数最少。均匀散列函数便是这样的函数。

均匀散列函数:假定散列有b个桶,且b>1,桶的序号从0到b-1。如果对所有的k,散列函数f(k) = 0,那么 f(k) 就不是一个均匀散列函数,因为它把所有的关键字都映射到一个0号桶里。这样的散列函数使冲突和溢出的数量最大。假设b = 11,关键字范围为[0,98]。一个均匀散列函数应该把大约每9个关键字映射到一个桶里。函数*f(k) =k%*对范围[0,r]内的关键字是均匀散列函数,其中r是正整数。

在实际应用中,关键字不是从关键字范围内均匀选择的,所以有的均匀散列函数表现好一些,有的差一些。那些在实际应用中性能表现好的均匀散列函数被称作良好散列函数


线性探查

  1. 方法

现有一个长度为8的散列表如下

01234567

关键字为12,48,57的键值对通过除法散列函数插入散列表中,得到如下表格

485712
01234567

此时如果想把关键字为28的键值对插入散列表中显然与12冲突并产生溢出,而处理最简单的方式是找到下一个可用的桶,这种解决溢出的方法叫做线性探查。

28因此被储存在5号桶,如下表

48571228
01234567

以此类推,所以在寻找可用桶时,把散列表当做一个环形表。

由此,可以设计散列表的搜索方法。假设要查找关键字为k的键值对,首先搜索起始桶f(k),然后把散列表当做环形表继续搜索下一个桶,知道以下情况之一发生为止:

a. 存有关键字k的桶已找到,即找到了要查找的键值对;

b.到达一个空桶;

c.回到起始桶f(k);

后两种情况说明关键字为k的键值对不存在。

删除一个键值对后要保证上述的搜索过程可以正常成进行,例如下散列表

485711122819
01234567

将28删除后,若是仅将散列表五号位置置为空,则无法搜索到19,因为通过19 % 8 = 3,然后从3号位置开始寻找19,在五号位置时便会发现空桶。所以在删除28时,除了将五号位的桶置为空,还需移动若干个桶(关键字与序列不匹配的),比如此处需要移动19到五号位置的桶处,直到到达一个空桶或回到删除位置为止。

散列表类的原型

在HashTable.h文件中,定义了pair节点,声明了HashTable类。

template<class K, class E>
struct pair {
    K key;
    E value;
};

template<class K, class E>
class HashTable {
private:
    pair<K, E> **table;
    int size;
    int length;
public:
    HashTable(int length);                   //构造函数

    ~HashTable();                            //析构函数

    pair<K, E> *find(const K &key) const;    //根据关键字返回键值对的指针

    int search(const K &key) const;          //根据关键字返回键值对在散列表的索引

    void insert(const pair<K, E> &pair);     //散列表的插入

    void del(const K &key);                  //根据关键字删除键值对

    int getSize() const;                     //返回散列表键值对个数
};

散列表的实现

#include "HashTable.h"
template<class K, class E>
HashTable<K, E>::HashTable(int length) {
    this->length = length;
    size = 0;
    table = new pair<K,E>* [length];
    for (int i = 0; i < length; ++i) {
        table[i] = nullptr;
    }
}

template<class K, class E>
HashTable<K, E>::~HashTable() {
    for (int i = 0; i < length; ++i) {
        if(table[i] != nullptr) {
            delete table[i];
            table[i] = nullptr;
        }
    }
    delete table[length];
}


template<class K, class E>
pair<K, E> *HashTable<K, E>::find(const K &key) const {
    int i = key % length;
    int j = i;
    do {
        if(table[j]->key == key) return table[j];
        else if(table[j] == nullptr) return nullptr;
        else{
            j = (j+1) % length;
        }
    } while (j != i);
    return nullptr;
}

template<class K, class E>
int HashTable<K, E>::search(const K &key) const {
    int i = key % length;
    int j = i;
    do {
        if(table[j]->key == key ) return j;
        else if(table[j] == nullptr) return -1;
        else{
            j = (j+1) % length;
        }
    } while (j != i);
    return -1;
}

template<class K, class E>
void HashTable<K, E>::insert(const pair<K, E> &tpair) {
    int i = tpair.key % length;
    int j = i;
    do {
        if(table[i] == nullptr){
            table[i] = new pair<K,E>;
            table[i]->key = tpair.key;
            table[i]->value = tpair.value;
            size++;
            break;
        } else if(table[i]->key == tpair.key){
            table[i]->value = tpair.value;
            break;
        } else{
            i = (i+1) % length;
        }
    } while (i != j);
}

template<class K, class E>
void HashTable<K, E>::del(const K &key) {
    int i = key % length;
    int j = i;
    do {
        if(table[i] != nullptr && table[i]->key == key){
            delete table[i];
            table[i] = nullptr;
            int k = i;
            do {
                k = (k+1) % length;
                if(table[k] == nullptr) break;
                else if(table[k]->key % length == j){
                    pair<K,E> * p = table[k];
                    table[k] = nullptr;
                    size --;
                    insert(*p);
                }
            } while (k != i);
            break;
        } else i = (i + 1) % length;
    } while (i != j);
    size--;
}

template<class K, class E>
int HashTable<K, E>::getSize() const {
    return size;
}

测试程序:

#include<iostream>

int main(){
    HashTable<int, int> hashTable(5);
    hashTable.insert(pair<int,int>{7,7});
    hashTable.insert(pair<int,int>{8,8});
    hashTable.insert(pair<int,int>{13,13});
    std::cout << "key = 8 index = " << hashTable.search(8) << std::endl;
    std::cout << "key = 7 index = " << hashTable.search(7) << std::endl;
    std::cout << "key = 13 index = " << hashTable.search(13) << std::endl;
    std::cout << "size = " << hashTable.getSize() << std::endl;
    hashTable.del(8);
    std::cout << "key = 13 index = " << hashTable.search(13) << std::endl;
    std::cout << "size = " << hashTable.getSize() << std::endl;
    hashTable.del(7);
    std::cout << "size = " << hashTable.getSize() << std::endl;
    return 0;

测试结果:

key = 8 index = 3
key = 7 index = 2
key = 13 index = 4
size = 3
key = 13 index = 3
size = 2
size = 1
  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+据集+超详细注释.zip

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值