集合中元素相加为给定值的算法_前端算法之散列算法

算法(广义)的时间是很奇妙的,将复杂的业务逻辑,可以通过算法转换成一个程序,如此,我们就可以不用为复杂的业务逻辑写一大堆命令式代码,而只需封装一个模块,提供一个接口就能解决复杂的业务逻辑。只需要我们学习足够多的算法和数据结构,在需要的时候,可以想到它们。今天我们来学习一种数据结构:散列表。

狭义的算法常用的有排序、递归、动态规划、贪心算法、散列算法等等,数据结构常用的有数组、对象、堆栈、队列、链表、集合(Set 类)、字典(Map 类)、散列表(以及散列集合)、二叉树等等。

散列算法的作用是尽可能快地在数据结构中找到一个值。散列函数的作用是给定一个健值,然后返回值在表中的地址。你是否知道数组和链表的差别呢?前者是检索很快,有一个下标就能快速定位到值,但是插入和删除项就没有后者强,而后者不需要像数组一样,改动一个项,其他的项在内存中的位置也会跟着变化,但是检索却更慢,因为要从头部或者尾部开始寻找。

那么如果像数组、对象或者是集合这些数据结构,配合散列算法(就是散列函数)使用的话,那么可以达到插入和检索的性能都很高的效果。

一个具体的场景:我们要维护一个数据结构,这个结构要存储以健为人名,以值为邮箱,业务需求是要不断往里面新增新的项,或者删除项,并且检索的频率也很高。先了解下 "lose lose" 散列函数,方法是简单地将每个健值中的每个字符的 ASCII 值相加。

如图所示,最终的 key 的存储方式就是每个字符的 ASCII 值相加的结果:

d9a9cd547333d8d71a7b385cab845743.png
    //loss loss 散列函数
    let loseloseHashCode = function(key) {
      let hash = 0;
      for(let i=0; i<key.length; i++) {
        hash += key.charCodeAt(i);
      }
      return hash % 37;  //取模意味着这个数据结构最多存放 36 个元素
    };

    //开创创建一个散列表吧
    function HashTable() {
      let items = [];
      this.put = function(key, value) {
        let position = loseloseHashCode(key);
        console.log(`${position} --- ${key}`);
        items[position] = value;
      };
      this.remove = function(key) {
        let position = loseloseHashCode(key);
        items[position] = undefined;
      };
      this.get = function(key) {
        let position = loseloseHashCode(key);
        return items[position];
      };
    };

    let hash = new HashTable();
    hash.put('Gandalf', 'gandalf@emai.com');
    hash.put('John', 'johnsnow@email.com');
    hash.put('Tyrion', 'tyrion@email.com');
    console.log(hash.get('Gandalf'));
    hash.remove('John');
    console.log(hash.get('John'));
但是你会发现散列表是有冲突的,比如如果加入的人名很多时,会造成人名得到的 ASCII 值相加的结果是一样的,比如 Tyrion 和 Aaron 就有相同的散列值(都为 16),Donnie 和 Ana 有相同的散列值(都为 13),那么上面的散列表就会发生冲突,后面加入的项会覆盖前面的项,也就是说前面的项在数据结构中消失了,发生这种情况时,就要想办法去解决它,处理冲突的几种方式:分离链接、线性探查和双散列法。
    //对 HashTable 类进行改写,分离链接法,即每个值是一个单链表的数据结构
    function HashTable() {
      let items = [];
      let valuePair = function(key, value) {
        this.key = key;
        this.value = value;
        this.toString = function() {
          console.log(`${key} --- ${value}`);
        };
      };

      this.put = function(key, value) {
        let position = loseloseHashCode(key);
        console.log(`${position} --- ${key}`);
        if(table[position] === undefined) table[position] = new LinkedList();
        table[position].append(new valuePair(key, value));
      };
      this.remove = function(key) {
        let position = loseloseHashCode(key);
        if(table[position] !== undefined) {
          let cur = table[postion].getHead();
          while(cur.next) {
            if(cur.element.key === key) {
              table[position].remove(cur.element);
              if(table[postion].isEmpty) {
                table[position] = undefined;
              }
              return true;
            }
            cur = cur.next;
          }
          //为链表的第一项或者最后一项
          if(cur.element.key === key) {
            table[position].remove(cur.element);
            if(table[postion].isEmpty) {
              table[position] = undefined;
            }
            return true;
          }
        }
        return false;
      };
      this.get = function(key) {
        let position = loseloseHashCode(key);
        if(table[position] !== undefined) {
          let cur = table[position].getHead();
          while(cur.next) {
            if(cur.element.key === key) {
              return cur.element.value;
            }
            cur = cur.next;
          }
          if(cur.element.key === key) {
            return cur.element.value;
          }
        }
        return undefined;
      };
    };
    //线性查找
    function HashTable2() {
      let table = [];
      let ValuePair = function(key, value) {
        this.key = key;
        this.value = value;

        this.toString = function() {
          console.log(`${key} ---- ${value}`);
        }
      };
      this.put = function(key, value) {
        let position = loseloseHashCode(key);
        if(table[position] === undefined) table[position] = new ValuePair(key, value);
        let index = ++position;
        while(table[index] !== undefined) {
          index++;
        }
        table[index] = new ValuePair(key, value);
      };
      this.remove = function(key) {
        let position = loseloseHashCode(key);
        if(table[position] !== undefined) {
          let index = ++position;
          while((table[index] === undefined || table[index].key !== key) && index < 36) {
            index++;
          }
          if(table[index] && table[index].key === key) {
            table[index] = undefined;
            return true;
          }
        }
        return false;
      };
      this.get = function(key) {
        let position = loseloseHashCode(key);
        if(table[position] !== undefined) {
          //这个位置上的值是否就是我们要查找的值,如果不是就往下一个位置查找
          if(table[position].key ===key) {
            return table[position].value;
          }else {
            let index = ++position;
            while((table[index] === undefined || table[index].key !== key) && index < 36) {
              index++;
            }
            if(table[index] && table[index].key === key) {
              return table[index].value;
            }
          }
        }
        return undefined;
      };
    }
    //创建更好的散列函数,djb2
    let djb2HashCode = function(key) {
      let hash = 5381;
      for(let i=0; i<key.length; i++) {
        hash = hash * 33 + key.charCodeAt(i);
      }
      return hash % 1013;  //如果最大限度是 1000 的话,就要比 1000 大
    };
使用 djb2 散列函数比 lose lose 散列函数发生的冲突要低很多,这是一个表现良好的散列函数,插入和检索元素的时间更好,也包括更低的冲突可能性。当然这并不是最好的散列函数,但是这是最受社区推崇的散列函数之一。

所以下次面试时,如果有人问题你,数组和链表的差别,你回答完之后,如果又问你,如果检索和插入以及删除的频率都高的话,怎么办呢?现在知道该怎么回答了吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值