数据结构四:散列表+字符串(DataWhale系列)

简介: Datawhale 系列数据结构Task4.1 散列表基本概念散列表(Hash Table,又叫哈希表),是根据关键码值(Key Value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。

Datawhale 系列数据结构

Task4.1 散列表

基本概念

散列表(Hash  Table,又叫哈希表),是根据关键码值(Key  Value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

散列表思想

(1)使用散列函数将给定键转化为一个“数组的索引”,理想情况下,不同的key会被转化为不同的索引,但是在实际情况中,我们会遇到不同的键转化为相同索引的情况,这种情况叫做散列冲突/碰撞,后文中会详细讲解;
(2)得到了索引后,我们就可以像访问数组一样,通过这个索引访问到相应的键值对。

如何设计散列函数

(1)散列函数的设计不能太复杂:减少计算时间
(2)散列函数整成的值要尽可能随机并且均匀分布
主要方法有:
    a.直接寻址法
    b.数字分析法
    c.平方取中法
    d.折叠法
    e.随机数法
    f.除留取余法

散列冲突

再好的散列函数也无法避免散列冲突
主要方法有:
    直接寻址法
    链表法:更常用,4.1.1基于其设计散列表

4.1.1实现一个基于链表解决冲突问题的散列表

/*布谷鸟散列概述
    使用hashA、hashB计算对应的key位置:
    1、两个位置均为空,则任选一个插入; 
    2、两个位置中一个为空,则插入到空的那个位置 
    3、两个位置均不为空,则踢出一个位置后插入,被踢出的对调用该算法,再执行该算法找其另一个位置,循环直到插入成功。 
    4、如果被踢出的次数达到一定的阈值,则认为hash表已满,并进行重新哈希rehash
    cuckoo hashing的哈希函数是成对的(具体的实现可以根据需求设计),每一个元素都是两个,分别映射到两个位置,一个是记录的位置,另一个是备用位置。这个备用位置是处理碰撞时用的,cuckoo hashing处理碰撞的方法,就是把原来占用位置的这个元素踢走,不过被踢出去的元素还有一个备用位置可以安置,如果备用位置上还有人,再把它踢走,如此往复。直到被踢的次数达到一个上限,才确认哈希表已满,并执行rehash操作
*/
interface HashFamily<AnyType>{
    //根据which来选择散列函数,并返回hash值
    int hash(AnyType x,int which);
    //返回集合中散列的个数
    int getNumberOfFunctions();
    //获取新的散列函数
    void generateNewFunctions();
}

class CuckooHashTable<AnyType>{
    //定义最大装填因子为0.4
      private static final double MAX_LOAD = 0.4;
      //定义rehash次数达到一定时,进行再散列
      private static final int ALLOWED_REHASHES = 1;
      //定义默认表的大小
      private static final int DEFAULT_TABLE_SIZE = 101;
      //定义散列函数集合
      private final HashFamily<? super AnyType> hashFunctions;
      //定义散列函数个数
      private final int numHashFunctions;
      //定义当前表
      private AnyType[] array;
      //定义当前表的大小
      private int currentSize;
      //定义rehash的次数
      private int rehashes = 0;
      //定义一个随机数
      private Random r = new Random();
      
      public CuckooHashTable(HashFamily<? super AnyType> hf){
          this(hf, DEFAULT_TABLE_SIZE);
      }
      public void printArray() {
        // TODO Auto-generated method stub
        
    }
    //初始化操作
      public CuckooHashTable(HashFamily<? super AnyType> hf, int size){
          allocateArray(nextPrime(size));
          doClear();
          hashFunctions = hf;
          numHashFunctions = hf.getNumberOfFunctions();
      }

      private int nextPrime(int size) {
        return size*2;
    }
    public void makeEmpty(){
          doClear();
      }
      //清空操作
      private void doClear(){
          currentSize = 0;
          for (int i = 0; i < array.length; i ++){
              array[i] = null;
          }
      }
      //初始化表
      @SuppressWarnings("unchecked")
    private void allocateArray(int arraySize){
          array = (AnyType[]) new Object[arraySize];
      }
      /**
       *
       * @param x 当前的元素
       * @param which 选取的散列函数对应的位置
       * @return
       */
      private int myHash(AnyType x, int which){
          //调用散列函数集合中的hash方法获取到hash值
          int hashVal = hashFunctions.hash(x, which);
          //再做一定的处理
          hashVal %= array.length;
          if (hashVal < 0){
              hashVal += array.length;
          }
          return hashVal;
      }
      /**
       * 查询元素的位置,若找到元素,则返回其当前位置,否则返回-1
       * @param x
       * @return
       */
      private int findPos(AnyType x){
          //遍历散列函数集合,因为不确定元素所用的散列函数为哪个
          for (int i = 0; i < numHashFunctions; i ++){
              //获取到当前hash值
              int pos = myHash(x, i);
              //判断表中是否存在当前元素
              if (array[pos] != null && array[pos].equals(x)){
                  return pos;
              }
          }
          return -1;
      }
    public boolean contains(AnyType x){
          return findPos(x) != -1;
      }
    /**
       * 删除元素:先查询表中是否存在该元素,若存在,则进行删除该元素
       * @param x
       * @return
       */
      public boolean remove(AnyType x){
          int pos = findPos(x);
          if (pos != -1){
              array[pos] = null;
              currentSize --;
          }
          return pos != -1;
      }

    /**
       * 插入:先判断该元素是否存在,若存在,在判断表的大小是否达到最大负载,
       * 若达到,则进行扩展,最后调用insertHelper方法进行插入元素
       * @param x
       * @return
       */
      public boolean insert(AnyType x){
          if (contains(x)){
              return false;
          }
          if (currentSize >= array.length * MAX_LOAD){
              expand();
          }
          return insertHelper(x);
      }

    private boolean insertHelper(AnyType x) {
            //记录循环的最大次数
            final int COUNT_LIMIT = 100;
            while (true){
                //记录上一个元素位置
                int lastPos = -1;
                int pos;
                //进行查找插入
                for (int count = 0; count < COUNT_LIMIT; count ++){
                    for (int i = 0; i < numHashFunctions; i ++){
                        pos = myHash(x, i);
                        //查找成功,直接返回
                        if (array[pos] == null){
                            array[pos] = x;
                            currentSize ++;
                            return true;
                        }
                    }
                    //查找失败,进行替换操作,产生随机数位置,当产生的位置不能与原来的位置相同
                    int i = 0;
                    do {
                        pos = myHash(x, r.nextInt(numHashFunctions));
                    } while (pos == lastPos && i ++ < 5);
                    //进行替换操作
                    AnyType temp = array[lastPos = pos];
                    array[pos] = x;
                    x = temp;
                }
                //超过次数,还是插入失败,则进行扩表或rehash操作
                if (++ rehashes > ALLOWED_REHASHES){
                    expand();
                    rehashes = 0;
                } else {
                    rehash();
                }
            }
        }

    private void expand(){
            rehash((int) (array.length / MAX_LOAD));
        }

        private void rehash(){
            hashFunctions.generateNewFunctions();
            rehash(array.length);
        }

        private void rehash(int newLength){
            AnyType [] oldArray = array;
            allocateArray(nextPrime(newLength));
            currentSize = 0;
            for (AnyType str : oldArray){
                if (str != null){
                    insert(str);
                }
            }
        }

}

4.1.2 实现一个LRU缓存淘汰算法

class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {  
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private final int maxCapacity;  
   
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
   
    private final Lock lock = new ReentrantLock();  
   
    public LRULinkedHashMap(int maxCapacity) {  
        super(maxCapacity, DEFAULT_LOAD_FACTOR, true);  
        this.maxCapacity = maxCapacity;  
    }  
   
    @Override 
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {  
        return size() > maxCapacity;  
    }  
    @Override 
    public boolean containsKey(Object key) {  
        try {  
            lock.lock();  
            return super.containsKey(key);  
        } finally {  
            lock.unlock();  
        }  
    }  
   
       
    @Override 
    public V get(Object key) {  
        try {  
            lock.lock();  
            return super.get(key);  
        } finally {  
            lock.unlock();  
        }  
    }  
   
    @Override 
    public V put(K key, V value) {  
        try {  
            lock.lock();  
            return super.put(key, value);  
        } finally {  
            lock.unlock();  
        }  
    }  
   
    public int size() {  
        try {  
            lock.lock();  
            return super.size();  
        } finally {  
            lock.unlock();  
        }  
    }  
   
    public void clear() {  
        try {  
            lock.lock();  
            super.clear();  
        } finally {  
            lock.unlock();  
        }  
    }  
   
    public Collection<Map.Entry<K, V>> getAll() {  
        try {  
            lock.lock();  
            return new ArrayList<Map.Entry<K, V>>(super.entrySet());  
        } finally {  
            lock.unlock();  
        }  
    }  
}

4.1.3 练习:两数之和

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

TASK4.2 字符串

4.2.1 实现一个字符集,只包含这26个英文字母的Trie树

class Trie_Tree{
     
    private class Node{
        private int dumpli_num;////该字串的反复数目,  该属性统计反复次数的时候实用,取值为0、1、2、3、4、5……
        private int prefix_num;///以该字串为前缀的字串数。 应该包含该字串本身。。!
        private Node childs[];////此处用数组实现,当然也能够map或list实现以节省空间
        private boolean isLeaf;///是否为单词节点
        public Node(){
            dumpli_num=0;
            prefix_num=0;
            isLeaf=false;
            childs=new Node[26];
        }
    }    
    
    
    private Node root;///树根  
    public Trie_Tree(){
        ///初始化trie 树
        root=new Node();
    }
    
    
    
    /**
     * 插入字串。用循环取代迭代实现
     * @param words
     */
    public void insert(String words){
        insert(this.root, words);
    }
    /**
     * 插入字串,用循环取代迭代实现
     * @param root
     * @param words
     */
    private void insert(Node root,String words){
        words=words.toLowerCase();////转化为小写
        char[] chrs=words.toCharArray();
        
        for(int i=0,length=chrs.length; i<length; i++){
            ///用相对于a字母的值作为下标索引,也隐式地记录了该字母的值
            int index=chrs[i]-'a';
            if(root.childs[index]!=null){
                ////已经存在了,该子节点prefix_num++
                root.childs[index].prefix_num++;
            }else{
                ///假设不存在
                root.childs[index]=new Node();
                root.childs[index].prefix_num++;                
            }    
            
            ///假设到了字串结尾,则做标记
            if(i==length-1){
                root.childs[index].isLeaf=true;
                root.childs[index].dumpli_num++;
            }
            ///root指向子节点,继续处理
            root=root.childs[index];
        }
        
    }
    
    /**
     * 遍历Trie树,查找全部的words以及出现次数
     * @return HashMap<String, Integer> map
     */
    public HashMap<String,Integer> getAllWords(){
//        HashMap<String, Integer> map=new HashMap<String, Integer>();
            
        return preTraversal(this.root, "");
    }
    
    /**
     * 前序遍历。。。
     * @param root        子树根节点
     * @param prefixs    查询到该节点前所遍历过的前缀
     * @return
     */
    private  HashMap<String,Integer> preTraversal(Node root,String prefixs){
        HashMap<String, Integer> map=new HashMap<String, Integer>();
        
        if(root!=null){
            
            if(root.isLeaf==true){
            ////当前即为一个单词
                map.put(prefixs, root.dumpli_num);
            }
            
            for(int i=0,length=root.childs.length; i<length;i++){
                if(root.childs[i]!=null){
                    char ch=(char) (i+'a');
                    ////递归调用前序遍历
                    String tempStr=prefixs+ch;
                    map.putAll(preTraversal(root.childs[i], tempStr));
                }
            }
        }        
        
        return map;
    }

    /**
     * 推断某字串是否在字典树中
     * @param word
     * @return true if exists ,otherwise  false 
     */
    public boolean isExist(String word){
        return search(this.root, word);
    }
    /**
     * 查询某字串是否在字典树中
     * @param word
     * @return true if exists ,otherwise  false 
     */
    private boolean search(Node root,String word){
        char[] chs=word.toLowerCase().toCharArray();
        for(int i=0,length=chs.length; i<length;i++){
            int index=chs[i]-'a';
            if(root.childs[index]==null){
                ///假设不存在,则查找失败
                return false;
            }            
            root=root.childs[index];            
        }
        
        return true;
    }
    
    /**
     * 得到以某字串为前缀的字串集。包含字串本身。 相似单词输入法的联想功能
     * @param prefix 字串前缀
     * @return 字串集以及出现次数,假设不存在则返回null
     */
    public HashMap<String, Integer> getWordsForPrefix(String prefix){
        return getWordsForPrefix(this.root, prefix);
    }
    /**
     * 得到以某字串为前缀的字串集。包含字串本身。
     * @param root
     * @param prefix
     * @return 字串集以及出现次数
     */
    private HashMap<String, Integer> getWordsForPrefix(Node root,String prefix){
        HashMap<String, Integer> map=new HashMap<String, Integer>();
        char[] chrs=prefix.toLowerCase().toCharArray();
        ////
        for(int i=0, length=chrs.length; i<length; i++){
            
            int index=chrs[i]-'a';
            if(root.childs[index]==null){
                return null;
            }
            
            root=root.childs[index];
        
        }
        ///结果包含该前缀本身
        ///此处利用之前的前序搜索方法进行搜索
        return preTraversal(root, prefix);
    }   
}

4.2.2 实现朴素的字符串匹配算法

public static int indext(String src, String target) {
        return indext(src,target,0);
    }

    public static int indext(String src, String target, int fromIndex) {
        return indext(src.toCharArray(), src.length(), target.toCharArray(), target.length(), fromIndex);
    }

    //朴素模式匹配算法
    static int indext(char[] s, int slen, char[] t, int tlen, int fromIndex) {
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (tlen == 0) {
            return fromIndex;
        }
        if (slen == 0) {
            return -1;
        }
        int i = fromIndex;
        int j = 0;
        while (i <= slen && j <= tlen) {
            /*  cycle compare */
            if (s[i] == t[j]) {
                ++i;
                ++j;
            } else {
                /*  point back last position */
                i = i - j + 1;
                j = 0;
            }
        }
        if (j > tlen) {
            /*  found target string retun first index position*/
            return i - j;
        } else {
             /* can't find target  string and retun -1 */
            return -1;
        }
    }

3.2.3 练习:反转字符串

class Solution {
    public String reverseString(String s) {
      final char[] array = s.toCharArray();
      final int length = array.length;
      for (int i = 0; i < length / 2; i++) {
        char temp = array[i];
        array[i] = array[length - i-1];
        array[length - i-1] = temp;
      }
      return new String(array);
    }
}

3.2.3 练习:反转字符串里的单词

 public String reverseWords(String s) {
        String[] words = s.split(" ");
        StringBuilder sb = new StringBuilder();
        for(String word : words) {
            sb.append(swapWord(0, word.length()-1, word.toCharArray())).append(" ");
        }
        
        return sb.toString().trim();
    }
    
    public String swapWord(int s, int e, char[] c) {
        if(s >= e) {
            return String.valueOf(c);
        }
        
        char temp = c[s];
        c[s] = c[e];
        c[e] = temp;
        return swapWord(s+1, e-1, c);
    }

3.2.3 练习:字符串转换整数(stoi)

 public int myAtoi(String str) {
        //去除掉前后的空格
        String strr = str.trim();
        //存储最终过滤出来的字符串
        String strrr = null;
        //字符串不为空时并且字符串不全是空白字符串时才转换
        if(strr != null && strr.isEmpty() == false){
            char f = strr.charAt(0);
            //判断字符串中的第一个非空格字符是不是一个有效整数字符
            if(f >= '0' && f <= '9' || f == '+'|| f == '-'){
                strrr = strr.substring(0,1); // 把第一位放进去(只能是数字、正负号)
                //这时候循环只要数字,因为正负号只能出现在第一位
                for(int i = 1; i<strr.length();i++){
                    if(strr.charAt(i) >= '0' && strr.charAt(i) <= '9'){
                        strrr = strr.substring(0,i+1);
                    }
                    //这是遇到不符合要求的字符,直接忽略剩余元素
                    else{break;}
                }
            }
        }
        //判断最终字符串是否为空或则只有一个正负号
        if(strrr == null || strrr.equals("+") || strrr.equals("-"))
            //此时strrr是String对象,如果使用==比较则比较的时内存地址
            return 0;
        //最终转换成的数字
        int num = 0;
        //使用异常机制打印结果
        try{
            num = Integer.parseInt(strrr);
        }catch (Exception e){
            if(strrr.charAt(0) == '-')
                return Integer.MIN_VALUE;
            return Integer.MAX_VALUE;
        }
        return num;
    }

参考文章:

散列表参考文章:
https://blog.csdn.net/ynnusl/article/details/89343419
https://blog.csdn.net/u012124438/article/details/78230478
字符串参考文章:
https://www.cnblogs.com/lcchuguo/p/5194323.html
目录
相关文章
|
15天前
|
XML JSON NoSQL
Redis的常用数据结构之字符串类型
Redis的常用数据结构之字符串类型
19 0
|
2月前
|
存储 算法 Java
[Java]散列表的数据结构以及对象在JVM堆中的存储过程
[Java]散列表的数据结构以及对象在JVM堆中的存储过程
52 1
[Java]散列表的数据结构以及对象在JVM堆中的存储过程
|
4月前
|
设计模式 算法 Java
【数据结构和算法】确定两个字符串是否接近
这是力扣的1657题,难度为中等,解题方案有很多种,本文讲解我认为最奇妙的一种。复杂度分析:时间复杂度:O(max⁡{n1,n2}+Clog⁡C),其中 n1 和 n2 分别是字符串 word1 和 word2 的长度,C=26 是字符集大小。空间复杂度:O(C)。
40 1
|
4月前
|
存储 缓存 数据库
Python高级数据结构——散列表(Hash Table)
Python高级数据结构——散列表(Hash Table)
78 1
Python高级数据结构——散列表(Hash Table)
|
5月前
|
存储 算法 NoSQL
【霍罗维兹数据结构】多维数组 | 字符串 | 模式匹配
【霍罗维兹数据结构】多维数组 | 字符串 | 模式匹配
37 0
|
9天前
|
Python
python学习-函数模块,数据结构,字符串和列表(下)
python学习-函数模块,数据结构,字符串和列表
49 0
|
6月前
|
XML 存储 JavaScript
【JavaSE专栏89】Java字符串和XML数据结构的转换,高效灵活转变数据
【JavaSE专栏89】Java字符串和XML数据结构的转换,高效灵活转变数据
|
2月前
|
算法 Java 索引
数据结构之串—字符串而不是羊肉串
数据结构之串—字符串而不是羊肉串
34 0
|
2月前
|
存储 缓存 索引
【数据结构入门精讲 | 第十四篇】散列表知识点及考研408、企业面试练习(1)
【数据结构入门精讲 | 第十四篇】散列表知识点及考研408、企业面试练习(1)
25 0
|
2月前
|
自然语言处理 数据安全/隐私保护
【数据结构入门精讲 | 第十五篇】散列表知识点及考研408、企业面试练习(2)
【数据结构入门精讲 | 第十五篇】散列表知识点及考研408、企业面试练习(2)
23 0