第十章 哈希表,哈希映射

基础知识:

k1和k2映射到同一值域,也叫压缩映射,比如通排序就是一种特殊的哈希表

散列函数:

直接定址法

数字分析法

平方折中法

折叠法

随机数法

除留余数法  ×最好理解

冲突解决

开放定址法

拉链法  就是后面写一个链表继续下去,但是如果数据很多可能会退化成0(n)

双散列  如果这里冲突就顺着这个往下走0(1)

再散列  再次哈希,一直到找到空的为止 0(1)

 

在java中先实现hashmap再实现hashset,,起始就是hashmap是的把value忽略掉就是一个heshset了。

hashmap代码



import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

/*
 * @desc HashMap测试程序
 *
 * @author skywang
 */
public class test2 {

    public static void main(String[] args) {
        testHashMapAPIs();
    }

    private static void testHashMapAPIs() {
        // 初始化随机种子
        Random r = new Random();
        // 新建HashMap
        HashMap map = new HashMap();
        // 添加操作
        map.put("one", r.nextInt(10));
        map.put("one", r.nextInt(10));
        map.put("two", r.nextInt(10));
        map.put("three", r.nextInt(10));

        // 打印出map
        System.out.println("map:为" + map);  //打印map

        // 通过Iterator遍历key-value
        Iterator iter = map.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            System.out.println("next : " + entry.getKey() + " - " + entry.getValue());
        }

        // HashMap的键值对个数
        System.out.println("size:" + map.size());

        // containsKey(Object key) :是否包含键key
        System.out.println("contains key two : " + map.containsKey("two"));
        System.out.println("contains key five : " + map.containsKey("five"));

        // containsValue(Object value) :是否包含值value
        System.out.println("contains value 0 : " + map.containsValue(0));

        // remove(Object key) : 删除键key对应的键值对
        map.remove("three");

        System.out.println("map:" + map);

        // clear() : 清空HashMap
        map.clear();

        // isEmpty() : HashMap是否为空
        System.out.println((map.isEmpty() ? "map is empty" : "map is not empty"));
    }
}

定义一个接口

package org.lanqiao.algo.elementary._10_hash;

import java.util.Iterator;

public interface IMap {
  /*清除所有键值对*/
  void clear();

  /*key是否已经存在*/
  boolean containsKey(K key);

  /*value是否存在*/
  boolean containsValue(V value);

  /*根据key获得value*/
  V get(K key);

  /*map是否为空*/
  boolean isEmpty();

  /*所有key组成的数组*/
  MyHashSet keySet();

  /*存入键值对*/
  void put(K key, V value);

  /*把另外一个map中的所有键值对存入到当前map中*/
  void putAll(IMap map);

  /*根据key删除一个键值对*/
  V remove(K key);

  /*键值对的个数*/
  int size();

  /*所有的value组成的数组*/
  V[] values();

  Iterator iterator();
}

 写一个类调用这个接口并且将他完整化

 

package org.lanqiao.algo.elementary._10_hash;

import java.util.Iterator;

public class MyHashMap implements IMap {
  private int length = 16;

  private Node[] buckets = new Node[length];//桶
  private int size;

  @Override
  public void clear() {
    for (int i = 0; i < buckets.length; i++) {
      buckets[i] = null;
    }
  }

  @Override
  public boolean containsKey(K key) {
    int index = hash1(key);
    if (buckets[index] == null) {
      return false;
    } else {
      Node p = buckets[index];//相当于在链表中找key
      while (p != null) {
        K k1 = p.key;
        //借用java机制,hashcode和equals都来自于Object,用户可以改写这两个方法——制定对象相等的规则
        if (k1 == key || (k1.hashCode() == key.hashCode() && k1.equals(key))) {
          return true;
        }
        p = p.next;
      }
    }

    return false;
  }

  @Override
  public boolean containsValue(V value) {
    for (int i = 0; i < buckets.length; i++) {
      if (buckets[i] != null) {
        Node p = buckets[i];
        while (p != null) {
          if (p.value.equals(value))
            return true;
        }
      }
    }
    return false;
  }

  @Override
  public V get(K key) {
    int index = hash1(key);
    if (buckets[index] == null) {
      return null;
    } else {
      Node p = buckets[index];
      while (p != null) {
        K k1 = p.key;
        if (k1 == key || (k1.hashCode() == key.hashCode() && k1.equals(key))) {
          return p.value;
        }
        p = p.next;
      }
    }
    return null;
  }

  @Override
  public boolean isEmpty() {
    return size == 0;
  }

  @Override
  public MyHashSet keySet() {
    MyHashSet set = new MyHashSet<>();
    for (int i = 0; i < buckets.length; i++) {
      if (buckets[i] != null) {
        Node p = buckets[i];
        while (p != null) {
          set.add(p.key);
          p = p.next;
        }
      }
    }
    return set;
  }

  @Override
  public void put(K key, V value) {
    Node node = new Node<>(key, value);
    int index = hash1(key);//算出在桶中的位置
    if (buckets[index] == null) {//桶中没有东西
      buckets[index] = node;
      size++;
    } else {
      Node p = buckets[index];//链表的表头找到
      while (p != null) {
        K k1 = p.key;
        if (key == k1 || key.hashCode() == k1.hashCode() && key.equals(k1)) {
          p.value = value;//存在相同的key,则更新value
          break;
        }
        if (p.next == null) {
          p.next = node;
          size++;
          break;
        }
        p = p.next;
      }

    }
  }

  private int hash1(K key) {
    // return key.hashCode() % length;
    int h = 0;
    int seed = 31;//素数
    String s = key.toString();
    for (int i = 0; i != s.length(); ++i) {
      h = seed * h + s.charAt(i);
    }
    return h % length;
  }

  @Override
  public void putAll(IMap map) {

  }

  @Override
  public V remove(K key) {
    int index = hash1(key);//先定桶的位置
    if (buckets[index] == null) {
      return null;
    } else {
      Node p = buckets[index];//找到表头
      Node pre = p;

      while (p != null) {
        K k1 = p.key;
        if (k1.hashCode() == key.hashCode() && k1.equals(key)) {
          //移除
          if (p == pre) {
            buckets[index] = pre.next;
          } else {
            pre.next = p.next;
          }
          size--;
          return p.value;
        }
        pre = p;
        p = p.next;
      }
    }
    return null;
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public V[] values() {
    return null;
  }

  private class MapInterator implements Iterator {
    int i = 0;
    Node p = buckets[0];

    @Override
    public boolean hasNext() {
      while (this.i < length && p == null) {
        this.i++;
        if (this.i == length)
          p = null;
        else
          p = buckets[this.i];
      }
      //i是一个非空的桶,p是链表头
      return p != null;
    }

    @Override
    public Node next() {
      Node res = p;
      p = p.next;
      return res;
    }
  }

  @Override
  public Iterator iterator() {
    return new MapInterator();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < buckets.length; i++) {
      if (buckets[i] != null) {
        Node p = buckets[i];
        while (p != null) {
          sb.append("(" + p.key + "," + p.value + "),");
          p = p.next;
        }
      }
    }
    return sb.toString();
  }

  public class Node {
    public K key;
    public V value;

    public Node(K key, V value) {
      this.key = key;
      this.value = value;
    }

    private Node next;

    @Override
    public String toString() {
      return "BSTNode{" +
          "key=" + key +
          ", value=" + value +
          '}';
    }
  }
}

hashset代码

hashset的话存的时候也只存一个数,整体功能比较少 

package org.lanqiao.algo.elementary._10_hash;

import java.util.Iterator;

public interface IHashSet {
  void add(E key);

  void remove(E key);

  void clear();

  boolean contains(E key);

  boolean isEmpty();

  int size();

  Iterator iterator();
}

 

package org.lanqiao.algo.elementary._10_hash;

import java.util.Iterator;

public class MyHashSet implements IHashSet {
  private MyHashMap map = new MyHashMap<>();

  @Override
  public void add(E key) {
    map.put(key, null);
  }

  @Override
  public void remove(E key) {
    map.remove(key);
  }

  @Override
  public void clear() {
    map.clear();
  }

  @Override
  public boolean contains(E key) {
    return map.containsKey(key);
  }

  @Override
  public boolean isEmpty() {
    return map.isEmpty();
  }

  @Override
  public int size() {
    return map.size();
  }

  @Override
  public Iterator iterator() {
    Iterator iter = map.iterator();
    return new Iterator() {
      @Override
      public boolean hasNext() {
        return iter.hasNext();
      }

      @Override
      public E next() {
        return (E) iter.next().key;
      }
    };
  }

  @Override
  public String toString() {
    Iterator iterator = map.iterator();
    StringBuilder sb = new StringBuilder();
    while (iterator.hasNext()) {
      sb.append(iterator.next().key + ",");
    }
    return sb.toString();
  }
}

布隆过滤器 

在工业中应用比较多,用于判断hashset和hanshmap里有没有一个东西,主要针对海量东西中查找一个元素是否在一个集合中,

原理是:当一个元素加入一个集合中,通过k个散列函数将这个元素映射成一个位数组中的k个点,把他们设为1,检索时,我们只需要看看这些点是不是都是1就知道集合中有没有他了,如果这些点有任何一个0,则被检查元素一定不再,如果都是1,则很有可能在

这里代码我有不太懂是因为不知道问什么要写8个hash函数???



import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**简化版本的布隆过滤器的实现*/
public class test2 {
    public static final int NUM_SLOTS = 1024 * 1024 * 8;//位图的长度
    public static final int NUM_HASH = 8;//hash函数的个数,一个hash函数的结果用于标记一个位
    private BigInteger bitmap = new BigInteger("0");//位图

    public static void main(String[] args) {
        //测试代码
        test2 bf = new test2();
        ArrayList contents = new ArrayList<>();
        contents.add("sldkjelsjf");
        contents.add("ggl;ker;gekr");
        contents.add("wieoneomfwe");
        contents.add("sldkjelsvrnlkjf");
        contents.add("ksldkflefwefwefe");

        for (int i = 0; i < contents.size(); i++) {
            bf.addElement(contents.get(i));
        }
        System.out.println(bf.check("sldkjelsvrnlkjf"));
        System.out.println(bf.check("sldkjelnlkjf"));
        System.out.println(bf.check("ggl;ker;gekr"));
    }

    /**将message+n映射到0~NUM_SLOTS-1之间的一个值*/
    private int hash(String message, int n) {
        message = message + String.valueOf(n);
        try {
            MessageDigest md5 = MessageDigest.getInstance("md5");//将任意输入映射成128位(16个字节)整数的hash函数
            byte[] bytes = message.getBytes();
            md5.update(bytes);//update函数,,转化为字节
            byte[] digest = md5.digest();
            BigInteger bi = new BigInteger(digest);//至此,获得message+n的md5结果(128位整数)

            return Math.abs(bi.intValue()) % NUM_SLOTS;//可能会超出位图的长度
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(test2.class.getName()).log(Level.SEVERE, null, ex);
        }
        return -1;
        // return (int)Math.abs(HashFunctions.bernstein(message,NUM_SLOTS));
    }

    /*处理原始数据
     * 1.hash1(msg)标注一个位……  hash的值域0~NUM_SLOTS-1
     * */
    public void addElement(String message) {
        for (int i = 0; i < NUM_HASH; i++) {//为了简便,这里并没有真的去写8个hash函数,而是下面那个函数传入不同的i,函数就会不一样
            int hashcode = hash(message, i);//代表了hash1,hash2……hash8
            //结果,用于标注位图的该位为1
            if (!bitmap.testBit(hashcode)) {//如果还不为1  //testbit函数代表查看啊某一位是否为1
                //标注位图的该位为1
                bitmap = bitmap.or(new BigInteger("1").shiftLeft(hashcode));
            }
        }
    }

    public boolean check(String message) {
        for (int i = 0; i < NUM_HASH; i++) {
            int hashcode = hash(message, i);
            //hashcode代表一个位置
            if (!this.bitmap.testBit(hashcode)) {//有一个是0,就返回false
                //如果位图的该位为0,那么message一定不存在
                return false;
            }
        }
        return true;//不精确,有可能误判
    }
}

1、如何做关于大数据内存的统计

有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数

tip:一个字节(byte)是八个比特(bit)

思路:32位,一个字节=8位,所以32位是4个byte(4个字节) 用map的话key和value都是32位整数,意思就是一条数据是8个字节,,那么20*8 =160亿个字节,10亿个字节是一个G ,那么他就需要16G,,有时候提上会说内存限制不能超过2个G,,那么就要用哈希分流,思路是把20亿个分成16个文件,然后每个文件去找亿个top1,,最后拿16个top1 去比大小就可以了

2、32位无符号整数范围是0到2^32-1(429496795) ,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没有出现过得数,可以最多1g的内存,怎样找到没有出现过的数

思路一:可以像上面那个一样,分成很多小文件,这样一个一个的查,效率的确会低,因为要查40亿多次,,然后有一点点不确定的地方是只要一个小文件的containskey(x)没有,那其他的也一定没有????

思路二:2^10 * 2^10 *2^10  等于一个G 再乘2^2  就是4个G,,这是4个G的比特(bit) 再除以8是0.5个G的字节,即500mb(兆)的字节,,然后我去建立一个40亿的bitmap就可以了,去扫描标记就OK

3、找到100亿个URL中重复的URl以及搜索词汇的topk、问题

思路:假设每一个url占64个byte那就是6400亿,就是640G,这样一定不行

所以这里可以采取上面那种小文件,但是生成小文件时不可随意分割而是哈希散列,保证不同的情况一定出现在同一文件中而非不同文件,否则无法判断重复否,, topk问题也就是同时维护640个100的小顶堆,,生成之后比大小

 

你可能感兴趣的:(寒假算法专题总结)