数据结构算法(十一) 之 散列表查找(哈希表)

一、散列函数构造方法

  • 除留取余法

对于散列表长度为 m 的散列函数公式为:

f(key)= key mod p (p <= m)

mod 就是取余的方法。一般 p 为小于或等于表长(最好接近 m)的最小质数或者不包含小于 20 质因子的合数。

二、处理散列冲突的方法

  • 1. 开放地址法

一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

线性探测法:
fi(key)= (f(key)+ di)MOD m (di = 1,2,3,......,m-1)

  • 2. 链地址法

其实这个就是 HashMap 的实现原理,将所有关键字为同义词的记录存储在一个单链表中,散列表只需要存储所有同义词子表的头指针就行了。

三、查找的一些面试题

  • 剑指 Offer 面试题 3(Java 版):二维数组中的查找

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路: 首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。

也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除)行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。

show my code

/**
 * 二维数组的查找
 * @author innovator
 *
 */
public class BinaryArraySearch {

     /**
     * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
     * 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
     * 

* 规律:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束: * 如果该数字大于要查找的数字,剔除这个数字所在的列:如果该数字小于要查找的数字,剔除这个数字所在的行。 * 也就是说如果要查找的数字不在数组的右上角,则每-次都在数组的查找范围中剔除)行或者一列,这样每一步都可以缩小 * 查找的范围,直到找到要查找的数字,或者查找范围为空。 * * @param data 待查找的数组 * @param number 要查找的数 * @return 查找结果,true 找到,false 没有找到 */ public static boolean find(int[][] data,int number){ boolean found = false; //输入条件判断 if(data == null || data.length <1 || data[0].length <1){ return false; } //选取右上角的数字 int row = 0; int colum = data[0].length-1; //行的数量 int rows = data.length; //列的数量 int colums = data[0].length; //行数正向增加,列数减少,同时保证在数组内 while(row >=0 && row < rows && colum >=0 && colum < colums){ //右上角的数字和指定的数字相等 if(data[row][colum] == number){ found = true; break; }else if(data[row][colum] > number){ //右上角的数字大于指定的数字,可以去掉最后一列 colum --; }else{ //右上角的数字小于指定的数字,可以去掉所在的这一行 row ++; } } return found; } public static void main(String[] args){ int[][] matrix = { {1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15} }; System.out.println(find(matrix, 7)); // 要查找的数在数组中 System.out.println(find(matrix, 5)); // 要查找的数不在数组中 System.out.println(find(matrix, 1)); // 要查找的数是数组中最小的数字 System.out.println(find(matrix, 15)); // 要查找的数是数组中最大的数字 System.out.println(find(matrix, 0)); // 要查找的数比数组中最小的数字还小 System.out.println(find(matrix, 16)); // 要查找的数比数组中最大的数字还大 System.out.println(find(null, 16)); // 健壮性测试,输入空指针 } }

数据结构算法(十一) 之 散列表查找(哈希表)_第1张图片
结果
  • 剑指 Offer 面试题 35(Java 版):第一个只出现一次的字符

题目:在字符串中找出第一个只出现一次的字符。如输入 "abaccdef",则输出 'b'。

思路: 我们可以通过将扫描到的 char 当成哈希表的 key,它出现的次数作为 value。当我们扫描完一次后,所有字符出现的次数都已经检查完毕了,接下来只需要顺序输出哈希表的 Entry,如果出现的次数是 1,那么直接返回。整个过程的时间复杂度是 O(n) 和 O(1)。

show my code

/**
 * 寻找第一个不重复出现的字符
 * @author innovator
 *
 */
public class FirstNotRepeatChar {

    /**
     * 寻找第一个不重复出现的字符
     * @param s
     * @return
     */
    public static char findFirstNotRepeatChar(String s){
        
        if(s == null || s.length() <1){
            throw new IllegalArgumentException("String should not be null or empty");
        }
        
        //用 LinkedHashMap 存放每个字符出现的次数,同时保证扫描顺序
        Map times = new LinkedHashMap();
        //第一次扫描字符串
        for(int i=0;i> entries = times.entrySet();
        for(Map.Entry entry:entries){
            //只出现过一次
            if(entry.getValue() == 1){
                result = entry.getKey();
                return result;
            }
        }
        
        return result;
        
    }
    
    public static void main(String[] args) {
        System.out.println(findFirstNotRepeatChar("google")); // l
        System.out.println(findFirstNotRepeatChar("aabccdbd")); // '\0'
        System.out.println(findFirstNotRepeatChar("abcdefg")); // a
        System.out.println(findFirstNotRepeatChar("gfedcba")); // g
        System.out.println(findFirstNotRepeatChar("zzgfedcba")); // g
    }
}
结果
  • 剑指 Offer 面试题 40(Java 版):数组中只出现一次的数字

题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

思路: 这个题目在强调一个(或两个)数字只出现一次,其他的出现两次。这有什么意义呢?我们想到异或运算的一个性质:任何一个数字异或它自己都等于0。了解了异或去重的原理,而且知道如果只有一个只出现一次的数字的求法,我们便要想办法把它分为两个子数组,每个子数组中包含一个只出现一次的数字,其他的数字都出现了两次。

剑指 offer 上的思路很巧妙,依然从头到尾异或所有的数字,这样得到的结果实际上就是两个只出现了一次的数字异或的结果,我们在异或后的结果中找出其二进制中最右边为 1 的位,该位既然为 1,说明异或的两个数 字对应的该位肯定不同,必定一个为 1,一个为 0。因此我们可以考虑根据此位是否为 1 来划分这两个子数组,这样两个只出现一次的数字就分开了。

但我们还要保证出现两次的数字都分到同一个子数组中,肯定不能两个重复的数字分在两个不同的子数组中,这样得到的结果是不对的。很明显,相同的数字相同的位上的值是相同的,要么都为 1,要么都为 0,因此我们同样可以通过判断该位是否为 1 来将这些出现两次的数字划分到同一个子数组中,该位如果为 1,就分到一个子数组中,如果为 0,就分到另一个子数组中。这样就能保证每个子数组中只有一个出现一次的数字,其他的数字都出现两次,分别全部异或即可得到这两个只出现一次的数字。时间复杂度为 O(n)。

另外,所有元素异或后,在找出最右边为 1 的时,我用的比剑指 offer 上更简洁的代码,主要用到了下面的结论:

对于一个数字 X,X&(-X) 之后得到的数字,是把 X 中最右边的 1 保留下来,其他位全部为 0。注意,这里的 -X 是 X 的相反数,-X=~X+1,这里的 ~X 意思是对 X 所有位取反,不要将二者弄混了。

show my code

public class FindNumsAppearOnceTest {

    /**
     * 寻找只出现一次的数字
     * @param arr
     * @throws Exception 
     */
    public static void findNumsAppearOnce(int[] arr) throws Exception{
        
        if(arr == null || arr.length<2){
            throw new Exception("输入的数据不合法");
        }
        
        int i;
        int AllXOR = 0;
        
        //全部异或
        for(i=0;i
结果

你可能感兴趣的:(数据结构算法(十一) 之 散列表查找(哈希表))