简单介绍哈希表作用及程序举例

本文通过实例介绍哈希表的工作原理,包括如何使用哈希函数进行数据的存储和检索。并通过C++代码实现了基于链接地址法的哈希表,展示了查找和插入操作的具体流程。

转自:http://blog.youkuaiyun.com/songzi1111/article/details/10985429
转自:http://blog.youkuaiyun.com/yleek/article/details/8691938

举一个不太恰当的例子方便理解:
比如几个数字(1,11,25,47,57)和一个二维数组arr[10][10],
我们用除以10的余数作为这些数字的分类标准,并按照余数存入相应下标的二维数组里。
得到的结果是:
arr[1][0] == 1;arr[1][1] == 11;
arr[5][0] == 25;
arr[7][0] == 47;arr[7][1] == 57;
查找哈希表:
比如查找57,58,首先对他们除以10得到余数,分别是7和8,
57就会在arr[7]中从arr[7][0]进行遍历查找知道最后,当查找到arr[7][1]时找到匹配项,查找结束。
58就会在arr[8]中从arr[8][0],由于arr[8]没有数据,所以哈希表中没有匹配项。

Hash原理:
设要存储对象的个数为num, 那么我们就用len个内存单元来存储它们(len>=num); 以每个对象ki的关键字为自变量,用一个函数h(ki)来映射出ki的内存地址,也就是ki的下标,将ki对象的元素内容全部存入这个地址中就行了。这个就是Hash的基本思路。

现在我要存储4个元素 13 7 14 11
显然,我们可以用数组来存。也就是:a[1] = 13; a[2] = 7; a[3] = 14; a[4] = 11;
当然,我们也可以用Hash来存。下面给出一个简单的Hash存储:
先来确定那个函数。我们就用h(ki) = ki%5;(这个函数不用纠结,我们现在的目的是了解为什么要有这么一个函数)。
对于第一个元素 h(13) = 13%5 = 3; 也就是说13的下标为3;即Hash[3] = 13;
对于第二个元素 h(7) = 7 % 5 = 2; 也就是说7的下标为2; 即Hash[2] = 7;
同理,Hash[4] = 14; Hash[1] = 11;

现在我要你查找11这个元素是否存在。对于数组来说,一个for循环就可以了。
也就是说我们要找4次。

Hash找一下。首先,我们将要找的元素11代入刚才的函数中来映射出它所在的地址单元。也就是h(11) = 11%5 = 1 了。下面我们来比较一下Hash[1]是否等于11, 这个问题就很简单了。也就是说我们就找了1次。

下面是c++实现哈希表的代码 :

//////////////////////////
// 用连地址表示的哈希表
//////////////////////////
#include <iostream>
using namespace std;

template <typename T>
class Node
{
public:
    Node(T val = 0) : key(val), next(NULL), val(88)
    {
        // 为什么这里的赋值通过offset宏看不到,看到的只是初始化列表里的88
        val = 33;
    }

    int val;
    Node * next;
    T key;
};


template <typename T>
class HashTable
{
public:
    // 建立一个哈希表要指定大小和哈希函数
    HashTable(int size, int (*hashFunc)(const T &)) : m_size(size), m_hashFunc(hashFunc)
    {
        m_ht = new Node<T> * [m_size];
        for(int i = 0; i < m_size; ++i)
            m_ht[i] = NULL;
    }

    ~HashTable()
    {
        for(int i = 0; i < m_size; ++i)
        {
            Node<T> *p = m_ht[i];
            while(p != NULL)
            {
                Node<T> * cur = p;
                p = p->next;
                delete cur;
            }
        }
        delete m_ht;
    }

    T * Find(const T & x);
    void Insert(const T & x);

private:
    int m_size;
    Node<T> ** m_ht; // hash table
    int (*m_hashFunc)(const T &);
};

// 查找
template <typename T>
T * HashTable<T>::Find(const T & x)
{
    int pos = m_hashFunc(x);
    Node<T> * p = m_ht[pos];
    while(p != NULL)
    {
        if(p->key == x)
            return &p->key;
        p = p->next;
    }
    return NULL;
}

// 插入
template <typename T>
void HashTable<T>::Insert(const T & x)
{
    int pos = m_hashFunc(x);
    Node<T> ** p = &m_ht[pos];

    while(*p != NULL)
        p = &((*p)->next);

    *p = new Node<T>(x);
}

/////////////////////////////////////////////////
// 哈希函数,保正哈希值均匀的落在哈希表的空间内
/////////////////////////////////////////////////
int HashFunc(const int & x)
{
    return x % 10;
}

#define MEMOFF(struc, e) (size_t)&(((struc*)0)->e)

int main()
{
    HashTable<int> ht(10, HashFunc);
    ht.Insert(1);
    ht.Insert(2);
    ht.Insert(3);
    int* res = ht.Find(3);
    if (res != NULL)
    {
        cout << *res << endl;
        size_t offset = MEMOFF(Node<int>, key);
        Node<int> *s = (Node<int>*)((size_t)res - offset);
        cout << s->val << endl;
    }
    else
        cout << "not found" << endl;

    return 0;
}

### 哈希表的应用场景 哈希表作为一种高效的数据结构,在许多领域都有广泛应用。它通过将键映射到数组中的索引来实现快速访问,因此特别适合于需要频繁执行查找、插入和删除操作的场合[^1]。 #### 应用场景举例 - **缓存系统**:在内存中维护最近使用的数据项以便快速检索。 - **数据库索引**:用于加速查询过程,特别是在涉及主键或唯一约束的情况下。 - **编译器符号表管理**:存储变量名及其属性(如类型),便于语法分析阶段快速定位。 - **去重功能**:比如网页爬虫程序会利用HashSet来记录已抓取过的URL链接地址以防重复下载同一页面内容。 以上这些都依赖于哈希函数能够均匀分布输入值从而减少冲突概率这一特性[^2]。 ### 实现方式详解 以下是两种主流编程语言C++Java对于哈希表的具体实现: #### C++ 中的 STL unordered_map 类型作为标准容器之一提供了类似字典的功能: 下面展示如何构建一个简单的整数类型的key-value pair形式的hash table并完成基本增删查改动作: ```cpp #include <iostream> #include <unordered_map> int main(){ std::unordered_map<int, int> hashTable(10); // 初始化大小为10 // 插入元素 hashTable.insert({1, 10}); // 查找元素 auto it = hashTable.find(1); if(it != hashTable.end()){ std::cout << "Found value: " << (*it).second << "\n"; } // 删除元素 hashTable.erase(1); return 0; } ``` 此代码片段展示了创建具有固定容量的无序关联容器以及对其执行典型的操作序列。 #### Java 的 HashMap 使用链地址法解决碰撞问题: 当两个不同的关键字被散列到了同一个位置上时,则采用拉链法把它们组织成一条单向链表挂在该槽位下边. 这里给出一段关于声明不同泛型参数列表下的map对象差异解释说明: 虽然 `Map<String,Integer>` 和 `HashMap<String,Integer>` 都可以用来表示字符串->整数之间的映射关系,但是前者只是接口类型而后者才是具体类实例化后的产物;另外由于多态机制的存在使得我们可以向上转型赋值给父类引用变量但反过来却不行除非强制转换才行[^3]. ```java // 正确的方式 Map<String, Integer> mapInterface = new HashMap<>(); ((HashMap<String, Integer>) mapInterface).put("one", 1); // 错误示范 HashMap<String, Integer> hashMapInstance = (HashMap<String, Integer>)new Map<>(); // 编译错误因为无法直接实例化抽象类或者接口. ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值