數據結構四:散列表+字符串(DataWhale系列)

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章