散列表之链接法

  • 散列表之链接法
  • 散列表的定义
  • 散列表的基本操作
  • 散列表的编码实现
    • 散列表的设计
    • 主测试文件
    • 编译运行
  • 结论

注意:

本文中的所有代码你可以在这里

https://github.com/qeesung/algorithm/tree/master/chapter11/11-2/hashTable(这里会及时更新)

或者这里

http://download.csdn.net/detail/ii1245712564/8804203

找到

散列表之链接法

在之前的文章《散列表之直接寻址表》中介绍了散列表中一种最基本的类型,直接寻址表。直接寻址表是通过键值直接来寻找在在数组里面的映射位置,但是直接寻址表存在一个缺陷,在键值范围较大且元素个数不多的时候,空间利用率不高,比如现在我们只需要映射两个元素(key:1,data1)和(key:10000,data2),此时我们需要分配一个大小为10001的数组,将第一个元素放到数组的1位置,第二个元素放到数组的最后一个位置,剩下的所有数组位置都没有被用到,造成空间的浪费。

于是,针对上面的情况,我们想到了下面的解决方案

hash(key)={01,key=1,key=10000

通过散列函数hash,来将key映射到合适的位置上,即通过hash(key)来找到在数组上的位置,最后我们只需要一个大小为2的数组就可以了。

散列表的定义

在直接寻址的方式下,具有关键字k的元素被放到数组位置k中。在散列方式下,该元素放在数组的hash(k)位置中,即通过散列函数hash(hash function)传入键值k来计算元素在数组中合适的位置。

假设我们有一集合U={d1,d2,d3,...dn},集合中过的每一个元素di都有一个键值keyi和一个数组值datai;新建一个数组T=[0...m],遍历一遍集合U,将集合中的每一个元素都放到数组的hash(key)位置上。

这里的散列表大小m一般要比集合的大小U小的多,可以说是具有关键字k的元素被散列到hash(k),也就是说hash(k)是键值k的散列值。

散列表的基本操作

散列表的基本操作有:

  • INSERT:插入一个元素
  • DELETE:删除一个元素
  • SEARCH:搜索一个元素

在我们进行编码实现之前,先来考虑一下下面几个问题

问题1:散列函数hash该怎么设计?

回答1:散列函数的设计关乎到整个散列表的运行效率,所以不可等闲待之,需要针对输入的元素集合来设计合适的散列函数,这也是散列表中最大的难点,比如在之前的例子中,我们设计的散列函数很简单,如果输入的key是1,就返回0,如果输入的key是1000,那么就返回1.如果把这个集合运用到其他集合上,那么就是一个很差劲的散列函数



问题2:因为集合U的大小|U|远大于散列表的大小m,所以经常会发生不同键值却有相同散列值的情况,于是散列表的同一个位置就会发生冲突,这时应该怎么办?

回答2:我们可以采用链表的形式,将散列值相同的元素放入同一链表里面,再将列表挂在散列表的对应位置上。

散列表的编码实现

下面是用到的文件,我们只列出部分的文件的内容,你可以在这里获得全部内容:

https://github.com/qeesung/algorithm/tree/master/chapter11/11-2/hashTable

.
├── hash_table.cc 散列表源文件
├── hash_table.h 散列表头文件
├── hash_table_test.cc 散列表测试源文件
├── hash_table_test.h 散列表测试头文件
├── link_list.cc 链式链表源文件
├── link_list.h 链式链表头文件
├── list.h 链表接口文件
├── main.cc 主测试文件
├── Makefile
└── README.md

0 directories, 10 files

散列表的设计

hash_table.h

#ifndef  HASHTABLE_INC
#define  HASHTABLE_INC

#include "link_list.h"
#include <vector>

/**
 *        Class:  LinkHashTable
 *  Description:  链接法散列表
 */
template < class T >
class LinkHashTable
{
    public:
        /** ====================  LIFECYCLE     ======================================= */
        LinkHashTable (int table_size=0 , int(*_HASH)(const T &)=0);                             /** constructor */
        /** ====================  MUTATORS      ======================================= */
        bool hash_insert(const T &); /*插入操作*/
        bool hash_delete(const T &);  /*删除操作*/
        std::vector<T> hash_search(int) const; /*查找操作*/
        void clear();
        void printToVec(std::vector<std::vector<T> > & );
    private:
        /** ====================  DATA MEMBERS  ======================================= */
        int (*HASH)(const T &);// 定义一个散列函数指针
        LinkList<T> * array; //链表的数组
        const size_t table_size;// 散列表的大小
}; /** ----------  end of template class LinkHashTable  ---------- */

#include "hash_table.cc"
#endif   /* ----- #ifndef HASHTABLE_INC  ----- */

散列表的成员中有一个散列函数的指针HASH,用户在构造散列表的时候需要制定对应的散列函数,还有一个array指针,是用来指向散列表的起始地址的。

hash_table.cc

#include "link_list.h"
/**
 *       Class:  LinkHashTable
 *      Method:  LinkHashTable
 * Description:
 */
template < class T >
LinkHashTable < T >::LinkHashTable (int _table_size , int(*_HASH)(const T &)) :        table_size(_table_size),HASH(_HASH)
{
    array = new LinkList<T>[table_size]();
}  /** ----------  end of constructor of template class LinkHashTable  ---------- */

template < class T >
bool LinkHashTable<T>::hash_insert (const T & ele)
{
    int index = HASH(ele);
    if(index >= table_size || index <0)
        return false;
    return array[index].insert(ele);
}       /** -----  end of method LinkHashTable<T>::hash_insert  ----- */

template < class T >
bool LinkHashTable<T>::hash_delete (const T & ele)
{
    int index = HASH(ele);
    if(index >= table_size || index <0)
        return false;
    return array[index].remove(ele);
}       /** -----  end of method LinkHashTable<T>::hash_delete  ----- */

template < class T >
std::vector<T> LinkHashTable<T>::hash_search (int k) const
{
    if(k < 0 || k >= table_size)
        return std::vector<T>();
    std::vector<int> vec;
    array[k].printToVec(vec);
    return vec;
}       /** -----  end of method LinkHashTable<T>::hash_search  ----- */

template < class T >
void LinkHashTable<T>::clear ()
{
    for(int i =0 ; i < table_size ; ++i)
    {
        array[i].clear();
    }
    return ;
}       /** -----  end of method LinkHashTable<T>::clear  ----- */

template < class T >
void LinkHashTable<T>::printToVec (std::vector<std::vector<T> > & vec)
{
    for(int i =0 ; i < table_size ; ++i)
    {
        std::vector<int> tempVec;
        array[i].printToVec(tempVec);
        vec.push_back(tempVec);
    }
    return ;
}       /** -----  end of method LinkHashTable<T>::printToVec  ----- */

主测试文件

main.cc

#include <stdlib.h>
#include "hash_table_test.h"
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>

/**
 * +++  FUNCTION  +++
 *         Name:  main
 *  Description:  测试的主函数
 */
int main ( int argc, char *argv[] )
{
    CppUnit::TextUi::TestRunner runner;
    CppUnit::TestSuite * suite = new CppUnit::TestSuite();

    suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test insert by qeesung", &HashTableTest::test_insert));
    suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test delete by qeesung", &HashTableTest::test_delete));
    suite->addTest(new CppUnit::TestCaller<HashTableTest>("Test search by qeesung", &HashTableTest::test_search));
    runner.addTest(suite);
    runner.run("",true);
    return EXIT_SUCCESS;

    return EXIT_SUCCESS;
}               /** ----------  end of function main  ---------- */

编译运行

编译方法

  • 如果你安装了cppunit
make
  • 如果没有安装cppunit,你需要这样编译
g++ hash_table_test.cc youSourceFile.cc -o target

运行方法

./hashTest(or your target)

结论

散列表最重要的是设计一个性能良好的散列函数(hash function),这也是散列表的难点,如果散列函数设计差劲的话,有可能造成数据分布的不平衡,删除和查找操作性能变差。我们考虑最差劲的散列函数,就是将所有的键值都映射为同一个散列值,那么对散列表的查找和删除操作的运行时间将和操作一个链式链表一样,都为O(n)。我们将在后面的文章中详细讲解散列函数的设计,敬请关注(づ ̄3 ̄)づ╭?~

时间: 2024-10-12 05:08:19

散列表之链接法的相关文章

【散列表-直接定址法】含有卫星数据的直接定址法

数组T中的每个值都是指针,指针指向node结点,node结点中的元素为key,data. 代码: #include<stdio.h> #include<stdlib.h> typedef struct Node { int key; int data; }Node; typedef struct T { Node **table;//利用指针数组 int size; }T; void T_create(T *t) { printf("关键字全域U是多少?\n")

散列表的C语言实现-分离链接法

一:散列表的定义: 散列表的实现常常叫做散列,散列是一种用于以常数平均时间执行插入,查找,删除的技术,但是,那些需要元素间任何排序信息的操作将不会得到支持,如findmin,findmax等等.散列表的优点很明显,它的查询时间为常数,速度非常快,缺点就是元素间没有排序,对于一些需要排序的场合不适用.理想的散列表数据结构就是一个包含有关键字的具有固定大小的数组,用一个散列函数来跟据关键字的值来将关键字插入到表中.这是散列的基本想法,剩下的问题是要选择一个好的散列函数,当俩个关键字散列到同一个值得时

散列表(算法导论笔记)

散列表 直接寻址表 一个数组T[0..m-1]中的每个位置分别对应全域U中的一个关键字,槽k指向集合中一个关键字为k的元素,如果该集合中没有关键字为k的元素,则T[k] = NIL 全域U={0,1,…,9}中的每个关键字都对应于表中的一个下标值,由实际关键字构成的集合K={2,3,5,8}决定表中的一些槽,这些槽包含指向元素的指针,而另一些槽包含NIL 直接寻址的技术缺点非常明显:如果全域U很大,则在一台标准的计算机可用内存容量中,要存储大小为|U|的一张表T也许不太实际,甚至是不可能的.还有

散列表之完美散列

散列表之完美散列 完美散列perfect hashing 两级散列法 gperf工具来自动生成完美散列函数 gperf的安装 gperf关键字文件的书写格式 gperf应用举例 注意 本文中的所有代码你都可以在这里: https://github.com/qeesung/algorithm/tree/master/chapter11/11-5/gperf(这里会及时更新) 或者是这里: http://download.csdn.net/detail/ii1245712564/8936303 找到

分离链接法实现散列表

散列表是一种用于查找的数据结构.其基本思想来自于索引,也可以看成是数组的一种扩展.对于一些数据信息,比如说图片文件名,如果我们要查找某张图片,通常将图片名作为关键字进行搜索.这个时候是不可能把图片名直接当成数组下标的,因此可以将图片名关键字通过某个函数映射为某个地址,或地址偏移量.那么每次要查找图片的时候直接输入关键字就能直接计算得出存储地址.其定义 T=h(k) 其中k为关键字,h为映射函数,T为得到的散列表,得到的函数值为地址或地址偏移量. 如果不同关键字通过某函数得到的散列值(地址偏移量)

[C++]实现散列表的分离链接法的数据结构

散列表,英文叫做Hash Table,因此也叫哈希表,是一种根据关键字值来确定主存中存储位置的数据结构.通过一个散列函数(关于键值的函数),来确定存储该关键字的位置. 主要的方法有: 1.分离链接法(拉链法) 分离链接法的散列函数为 position = key % n. 即关键字的存储位置为关键字的值对表项的数量取模.若表项大小为13,对于关键值为27的项,存储在1(27 % 13 = 1)的表项处.为了减少冲突,n往往取素数.对于同余的关键字,采用 队列(链表) 的方式连接在一起,新放入的元

链接法(chaining)构建散列表(hash table)(C++实现)

问题 最近项目中遇到了一个分布式系统的并发控制问题.该问题可以抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 ... Ln组成:D本质上是一个key-value存储,它对外提供基于HTTP协议的CRUD操作接口.L的业务逻辑可以抽象为下面3个步骤: read: 根据keySet {k1, ... kn}从D获取keyValueSet {k1:v1, ... kn:vn} do: 根据keyValueSet进行业务处理,得到需要更新的数据集keyValueSet' {k1':v1

【散列表-链接法解决冲突】利用链接法来解决冲突的散列表

~~~~(>_<)~~~~,搞了好几天终于把这个做出来了.. 首先看一下这种散列表的结构: 1.每个槽都令其为NULL,注意里面保存的都是指向Node的指针,而不是结点哦~ 2.然后我这里把链表的头结点,比如上图的k1,k5,k8的prior指针指向了T这个散列表,因为这样删除的时候会比较简单. 3.注意删除链表中的第一个结点和尾结点时候的不同方法哦..因为这个耽误了3天时间啊... 好了,代码如下: #include<stdio.h> #include<stdlib.h&g

散列表的数学分析(精解)--通过链表法解决碰撞

散列表的具体实现就不多做介绍了,就是一个数组,每个下标存储的是碰撞的元素的链表头指针,如下图所示: 下面直接研究对用链接法散列的分析: 给定一个能存放n个元素的.具有m个槽位的散列表T,定义T的装载因子α为n/m,即一个链中平均存储的元素数. 用链接法散列的最坏情况性能很差:所有的n个关键字都散列到同一个槽中,从而产生出一个长度为n的链表.这时,最坏情况下查找的时间为O(n),再加上计算散列函数的时间,这么一来就和用一个链表来链接所有的元素差不多了.显然我们并不是因为散列表的最坏情况性能差才用它