数据结构与算法之散列

散列

基于数组进行设计的数据结构

优点:可以快速插入,删除和取用数据

缺点:查找操作效率低下

在使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。理想情况下从key到index应该是一一对应的,然而键的数量可以是无限的,而数组长度是有限的,因此一个更现实的目标是让散列函数尽量均匀地映射到数组中(即让两个或多个key对于1个index,这种现象称为碰撞)。

对数组大小常见的限制是: 数组长度应该是一个质数。也会有多种确定数组大小的策略, 所有的策
略都基于处理碰撞的技术。

散列函数

除留余数法:以数组的长度(质数)对键取余,取余数作为数组中的索引值。

比如对于字符串形式的数据可以计算每个字符ASCII码值的和:

simpleHash(data){
    var total = 0;
    for(var i = 0;i<data.length;i++){
        total += data.charCodeAt(i);
    }
    return total % this.table.length;
}

但如果某两个字符串的ASCII码值的和相等的时候就会发生前面提到的碰撞问题,此时只会有一个数据被保存。为了改善这个问题,我们需要一个更好的散列函数。

  1. 为了避免碰撞, 首先要确保散列表中用来存储数据的数组其大小是个质数
  2. 数组的长度应该在 100 以上(137), 这是为了让数据在散列表中分布得更加均匀
  3. 采用霍纳算法,在每次求和时乘以一个质数(31)。
betterHash(string){
    const H = 31;  //这个质数的选择决定了是否会发生碰撞,发生时应及时调整
    var total = 0;
    for(var i = 0;i<string.length;i++){
        total += H * total + string.charCodeAt(i);
    }
    total = total % this.table.length;
    // if(total < 0){
    //     total += this.table.length - 1;
    // }
    return total;
},

总结上述:

function HashTable(){
    this.table = new Array(137);
}

HashTable.prototype = {
    constructor: HashTable,

    //简单,容易产生碰撞
    simpleHash(data){
        var total = 0;
        for(var i = 0;i<data.length;i++){
            total += data.charCodeAt(i);
        }
        return total % this.table.length;
    },

    //采用霍纳算法,避免碰撞
    betterHash(string){
        const H = 37;
        var total = 0;
        for(var i = 0;i<string.length;i++){
            total += H * total + string.charCodeAt(i);
        }
        total = total % this.table.length;
        // if(total < 0){
        //     total += this.table.length - 1;
        // }
        return total;
    },
    showDistro(){
        var n = 0;
        for(var i = 0;i<this.table.length;i++){
            if(this.table[i] !== undefined){
                console.log(i + ': ' + this.table[i]);
            }
        }
    },
    put(key, data){
        var pos = this.betterHash(key);
        this.table[pos] = data;
    },
    get(key){
        return this.table[this.betterHash(key)];
    }
}
碰撞处理

这里介绍开链法和线性探测法两种。这两种方法如何取舍?如果数组的大小是待存储数据个数的 1.5 倍,使用开链法;如果数组的大小是待存储数据的两倍及两倍以上时,那么使用线性探测法。也就是说存储数据使用的数组长度越大,越适合使用线性探测法。

开链法:指实现散列表的底层数组中, 每个数组元素又是一个新的数据结构, 比如另一个数组, 这样就能存储多个键了。

实现方法:在创建存储散列过的键值的数组时, 通过调用一个函数创建一个新的空数组, 然后将该数组赋给散列表里的每个数组元素。 这样就创建了一个二维数组。(注意get和put方法重写)

function HashTable(){
    this.table = new Array(137);
    this.buildChains();
}

HashTable.prototype = {
    constructor: HashTable,

    //采用霍纳算法,避免碰撞
    betterHash(string){
        const H = 37;
        var total = 0;
        for(var i = 0;i<string.length;i++){
            total += H * total + string.charCodeAt(i);
        }
        total = total % this.table.length;
        return total;
    },

    buildChains(){
        for(var i = 0;i<this.table.length;i++){
            this.table[i] = new Array();
        }
    },
    showDistro(){
        var n = 0;
        for(var i = 0;i<this.table.length;i++){
            if(this.table[i][0] !== undefined){
                console.log(i + ': ' + this.table[i]);
            }
        }
    },
    put(key, data){
        var pos = this.betterHash(key);
        var index = 0;
        while(this.table[pos][index] !== undefined){
            index++;
        }
        this.table[pos][index] = data;
    },
    get(key){
        var index = 0;
        var pos = this.betterHash(key);
        while(this.table[pos][index] !== key){
            var current = this.table[pos][index];
            if(current === undefined){
                return undefined;
            }else{
                index++;
            }
        }
        return this.table[pos][index];
    },
}

线性探测法:隶属于一种更一般化的散列技术——开放寻址散列。当发生碰撞时,线性探测法检查散列表中的下一个位置是否为空。如果为空,就将数据存入该位置;如果不为空,则继续检查下一个位置,直到找到一个空的位置为止。

直接上代码(注意一下get方法的实现)。

function HashTable(){
    this.table = new Array(137);
    //与table并行存储,values存储值,table存储键
    this.values = [];
}

HashTable.prototype = {
    constructor: HashTable,

    //采用霍纳算法,避免碰撞
    betterHash(string){
        const H = 37;
        var total = 0;
        for(var i = 0;i<string.length;i++){
            total += H * total + string.charCodeAt(i);
        }
        total = total % this.table.length;
        return total;
    },

    showDistro(){
        var n = 0;
        for(var i = 0;i<this.values.length;i++){
            if(this.values[i] !== undefined){
                console.log(i + ': ' + this.values[i]);
            }
        }
    },
    put(key, data){
        var pos = this.betterHash(key);
        if(this.table[pos] === undefined){
            this.table[pos] = key;
            this.values[pos] = data;
        }else{
            while(this.table[pos] !== undefined){
                pos++;
            }
            this.table[pos] = key;
            this.values[pos] = data;
        }
    },
    get(key){
        var pos = this.betterHash(key);
        for(var i = pos;this.table[i] !== undefined;i++){
            if(this.table[pos] === key){
                return this.values[pos];
            }
        }
        return undefined;
    },
}

原文地址:https://www.cnblogs.com/simpul/p/11027179.html

时间: 2024-11-08 12:38:20

数据结构与算法之散列的相关文章

非对称算法,散列(Hash)以及证书的那些事

转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/35389657 作者:小马 这几个概念在金融电子支付领域用得比较多,我忽然觉得把它们串起来一起讲,层层引入,可能更好理解一些.希望能以最简单朴实的方式讲明白他们之间的关系. 一非对称算法 关于非对称算法,你只要知道下面这些就行了,密钥是一对,一个叫公钥,一个叫私钥,前者公开,后者保密.假设你有一对公私钥,给你一串数据,你可以用私钥加密,然后把密文和公钥都放出去,别人可以用这个公钥解

算法导论笔记——第十~十一章 数据结构(一) 散列

第十章 基本数据结构 栈:可由数组表示 队列:可由数组表示 指针和对象:可由多数组表示.可用栈表示free list 有根数: 二叉树:左右孩子 分支无限制:左孩子右兄弟表示法 第十一章 散列表 数组:为每个元素保留一个位置 散列表:用于实际存储关键字比全部可能关键字少很多时,比如字典操作 解决散列冲突:链接法,开放寻址法 11.2 散列表 用链表法,在简单均匀散列的假设下,一次成功或不成功的查找所需要的平均时间为Θ(1+α),α为load factor. 11.3 散列函数 好的散列函数应(近

数据结构与算法分析java——散列

1. 散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存入到此存储单元中.检索时,用同样的方法计算地址,然后到相应的单元里去取要找的结点.通过散列方法可以对结点进行快速检索.散列(hash,也称“哈希”)是一种重要的存储方式,也是一种常见的检索方法. 按散列存储方式构造的存储结构称为散列表(hash table).散列表中的一个位置称为槽(slot).散

散列算法与散列码

一.引入 1 /** 2 * Description:新建一个类作为map的key 3 */ 4 public class Groundhog 5 { 6 protected int number; 7 8 public Groundhog(){ 9 } 10 public Groundhog(int number) 11 { 12 this.number = number; 13 } 14 15 @Override 16 public String toString() 17 { 18 ret

数据结构和算法篇——散列表

之前讲过博主在某网买了一个数据结构与算法的课程.本篇散列表是其中的三节.散列表应该是 Java 程序员常用并且最先碰到的一个数据结构了吧?Java 的 HashMap 就是对散列表的实现.可以说散列表算是一个比较基础.比较好理解(抛开需要缜密设计的哈希函数不说).比较好用(查询时间复杂度O(1))的一种数据结构.本篇在此分享这三节的总结笔记. 1)散列表开篇介绍:https://www.cnblogs.com/christmad/p/11519055.html 2)如何打造一个工业级的散列表:h

数据结构学习笔记07散列查找

1.散列表(Hash) 查找的本质: 已知对象找位置. 有序安排对象:全序.半序 直接“算出”对象位置:散列 时间复杂度几乎是常量:O(1),即查找时间与问题规模无关 散列查找法的两项基本工作: 计算位置:构造散列函数确定关键词存储位置: 解决冲突:应用某种策略解决多个关键词位置相同的问题 散列(Hashing) 的基本思想是: ①以关键字key为自变量,通过一个确定的函数 h(散列函数),计算出对应的函数值h(key),作为数据对象的存储地址. ②可能不同的关键字会映射到同一个散列地址上,即h

数字签名、数字证书、对称加密算法、非对称加密算法、单向加密(散列算法)

数字签名是什么? 1. 鲍勃有两把钥匙,一把是公钥,另一把是私钥. 2. 鲍勃把公钥送给他的朋友们----帕蒂.道格.苏珊----每人一把. 3. 苏珊给鲍勃写信,写完后用鲍勃的公钥加密,达到保密的效果. 4. 鲍勃收信后,用私钥解密,看到信件内容. 5. 鲍勃给苏珊回信,写完后用Hash函数,生成信件的摘要(digest). 6. 然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature). 7. 鲍勃将这个签名,附在信件下面,一起发给苏珊. 8. 苏珊收信后,取下数

PTA数据结构与算法题目集(中文) 7-43字符串关键字的散列映射 (25 分)

PTA数据结构与算法题目集(中文)  7-43字符串关键字的散列映射 (25 分) 7-43 字符串关键字的散列映射 (25 分) 给定一系列由大写英文字母组成的字符串关键字和素数P,用移位法定义的散列函数(将关键字Key中的最后3个字符映射为整数,每个字符占5位:再用除留余数法将整数映射到长度为P的散列表中.例如将字符串AZDEG插入长度为1009的散列表中,我们首先将26个大写英文字母顺序映射到整数0~25:再通过移位将其映射为3:然后根据表长得到,即是该字符串的散列映射位置. 发生冲突时请

数据结构与算法----散列/哈希

1. 简介 散列表的实现叫散列hashing,散列用于以常数平均时间执行 插入.删除.查找,不支持排序.findMin.findMax. 查找关键字不需要 比较 在一个记录的存储位置和它的关键字之间建立映射关系:key--f(key)   这个关系就是散列函数/哈希函数.将一些记录存储在一块 连续 的存储空间,这块空间就是散列表/哈希表. 与线性表.树.图比较: 数据元素之间没有什么逻辑关系,也不能用连线图表示出来. 问题: 关键字不同,但通过散列函数计算的结果相同,即出现了冲突 collisi