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

一、散列函数构造方法

  • 除留取余法

对于散列表长度为 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 {

     /**
     * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
     * 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
     * <p/>
     * 规律:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束:
     * 如果该数字大于要查找的数字,剔除这个数字所在的列:如果该数字小于要查找的数字,剔除这个数字所在的行。
     * 也就是说如果要查找的数字不在数组的右上角,则每-次都在数组的查找范围中剔除)行或者一列,这样每一步都可以缩小
     * 查找的范围,直到找到要查找的数字,或者查找范围为空。
     *
     * @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));     // 健壮性测试,输入空指针
        
    }
}
结果
  • 剑指 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<Character,Integer> times = new LinkedHashMap();
        //第一次扫描字符串
        for(int i=0;i<s.length();i++){
            char c = s.charAt(i);
            
            //不包含
            if(!times.containsKey(c)){
                times.put(c, 1);
            }else{
                int value = times.get(c);
                times.put(c, value+1);
            }
        }
        
        Character result = '\0';
        //获取所有扫描的数据
        Set<Map.Entry<Character, Integer>> entries = times.entrySet();
        for(Map.Entry<Character, Integer> 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<arr.length;i++){
            
            AllXOR ^= arr[i];
        }
        

        //0x0001,找到最右边第一位为 1 的数
        int res = findFirstBit1(AllXOR);

        int num1 = 0;
        int num2 = 0;
        
        for(int k=0;k<arr.length;k++){
            
            //同一种类型
            if(isBit1(arr[k],res)){
                num1 ^= arr[k];
            }else {
                num2 ^= arr[k];
            }   
        }
        
        System.out.println("不重复的数字:"+num1);
        System.out.println("不重复的数字:"+num2);
    }
    
    /**
     * 返回 num 的最低位的 1,其他各位都为 0
     * 
     * 对于一个数字X,X&(-X)之后得到的数字,是把X中最右边的1保留下来,其他位全部为0。注意,
     * 这里的-X是X的相反数,-X=~X+1,这里的~X意思是对X所有位取反,不要将二者弄混了。
     * @param num
     * @return
     */
    public static int findFirstBit1(int num){
        
        //二者与后得到的数,将 num 最右边的 1 保留下来,其他位的全部置为了 0
        return num & (-num);
    }
    
    
    /**
     * 判断 num 中特定的位是否为 1,这里的要判断的特定的位由 res 确定,res 中只有一位为 1,
     * 其他位均为 0,由 findFirstBit1 函数返回,而 num 中要判断的位便是 res 中这唯一的 1 所在的位
     * 
     * 根据这个来将数组分成两个数组,保证每个数组只有一个数字只出现一次
     * @param num
     * @param res
     * @return
     */
    public static boolean isBit1(int num,int res){
        
        return ((num & res)==0) ? false:true;
    }
    
    
    public static void main(String[] args) throws Exception {
        int[] data = {
                2,4,3,6,3,2,5,5
        };
        
        findNumsAppearOnce(data);
    }
    
}
结果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 基本概念 基于线性表、树表结构的查找方法,这类查找方法都是以关键字的比较为基础的。在查找过程中只考虑各元素关键字之...
    官先生Y阅读 468评论 0 2
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,059评论 0 13
  • 1、强队震荡盘口假意造冷诱惑上盘。 5月15日,恒大VS上港.主近况不佳,上轮输球;客队5球大胜申花加分。初盘一球...
    可乐说阅读 893评论 0 1
  • 连着锻炼了几天,觉得整个过程特别充实。 也许是我太久没有认真做过一件事了,在宝宝的鼓励和开导下,我可能终于想通...
    上天的XLG阅读 209评论 0 1
  • 研一的时候还站在开题门外 却被老师们的气势给吓坏 他瞪着大大的眼睛那么难猜 刁钻的问题往外甩 每一项实验设计都会有...
    财迷财谜阅读 294评论 0 0