对Hash算法最原始的理解

首先来解决从前一直困扰我的一个问题
哈希算法究竟是如何提高查找效率的?
先回忆一下我最初的奇怪想法,假设我有一张表,表中有n个数据,我可以用不同的方法查找表中元素。好了,现在要采用hash,显然,新建hash表的大小不能够小于我原表的大小,那么我又有了一张hashtable,当需要查找元素的时候,在hashtable中去查找。
???我干嘛要新建一张hash表,不好好的去查我原表?

非也非也
在hash表中,我的每一个下标都是一个哈数值,也就是索引,任何你传进来的数据,我都能很快知道该数据存在hash表的哪一个位置(如果该数据不在表中,我也能很快的知道(而不是向原无序表一样遍历一次),快到时间复杂度O(1))
具体怎么做?直接看下面代码
描述:假设有100个数据,每个数据值都在[1,10000]之间且不重复,建表之后实现复杂度为O(1)的查找,对输入的每一个数字,立刻输出是否存在于表中。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Scanner;
public class MyHashTable {
    static int[] myHashArr=new int[300];  //新建hash表,设置大小为300
    static int hash(int x) {
        return x%300;  //hash算法采用取余法
    }
    static void put(int x) {  //存数进hash表的方法
        int value=x;
        while(true) {
            int key=hash(x);
            if(myHashArr[key]==0) {
                myHashArr[key]=value;
                break;
            }else x++;  //重定址
        }       
    }
    static void find(int x) {  //查找值
        int value=x,key;
        int times=0;
        do{
            times++;
            key=hash(x);
            if(myHashArr[key]==value) {
                System.out.println("   arr["+key+"]="+value+"  查找次数:"+times);
                return;
            }else x++;
            if(key==hash(value)&&value!=x-1)break;
        }while(myHashArr[key]!=0);
        System.out.println("    No data="+value+" found!"+"  查找次数:"+times);
    }
    static void getArrTxt() throws IOException {   //辅助函数,把数组输出到C盘的txt文件方便观察
        FileOutputStream fs = new FileOutputStream(new File(("C:\\Users\\Administrator\\Desktop\\arr.txt")));
        PrintStream p = new PrintStream(fs);
        int col=0;
        for(int i=0;i<300;i++) {
            p.print(myHashArr[i]);
            p.print(' ');
            col++;
            if(col==10) {
                col=0;
                p.print("\r\n");
            }
        }
        p.close();fs.close();
    }
    public static void main(String[] args) {
        HashSet<Integer> set=new HashSet<>();
        for(int i=0;i<100;i++) {
            int x=(int) Math.round(Math.random()*10000)+1;
            set.add(x);  //利用set消除重复元素
        }
        for(Integer i:set) 
            put(i);   //放入哈希数组
        try {getArrTxt();}catch(Exception e) {}
        System.out.println("开始查找吧:");
        Scanner read=new Scanner(System.in);
        int y;
        while((y=read.nextInt())!=0) 
            find(y);
        read.close();
    }
}

上述代码并不复杂,以下是某一次的运行结果,我们通过运行结果来说明hash为什么快

程序生成的txt文件(局部,一共有30行太长了,用不了那么多),和控制台截图如下

第一次查找 4568  ,定址  key=4568%300=68,表中第7行9列为0,查找完毕,No data

第四次查找1  ,定址key=1%300=1,表中第1行2列数据,有9901,对比一下,不是1,继续往后查,重定址key=(1+1)%300=2,1行3列为0,查找完毕, No data

最后一次查找3069,定址  key=3069%300=69,表中第7行10列为5469,不等于3069,继续往后查,重定址key=70(8行1列),5770,还是不等于3069,继续重定址key=71(8行2列),3069,查找成功 arr[71]=3069

Q1:为什么arr[key]=0就表示表中无数据?

A:如果表中存在待查数据,根据我们的hash()算法,该值对应的下标注定从key开始逐渐增加1,具体看put方法。

Q2:为什么第四次查找1,定址之后得到了别的值9901,往后找还没找到1?

A:这说明9901这个数先于1这个数被放入hash表,9901已经占据了下标1对应的位置。9901的下标1后面的下标2,对应的arr[2]=0,这不仅说明1这个数没有被加入数组(Q1),还说明除了9901以外没有任何满足300*m+1的数被放入hash表,而且还说明没有任何满足300*m+2的数被放入hash表。

Q3:为什么最后一次查找3069,最后却定位到了71?

A:根据规则3069会定位到69,但是如同Q2,该位置已经被先进入hash表中的数占据了,根据我们的put规则,key往后递增,直到发现没有被占据的key=71位置,则放入3069.显然,如果这个时候我们要放入3369,那么他只能占据如图,3069后面的后面的后面那个0(没被占据)的位置了。

OtherQ:为什么我们只有100个数据,hash表却设计了300的大小?为什么要采用取余计算哈希值,用其他的可以吗?如果说我运气不好,放入的某个数x一直取址取址都发现被占据,那是不是就无限循环了?

A:因为我们这里的hash表是用数组代替的,所以就把长度设置的比较大,如果长度设置为100的话,就真的会出现你说的运气不好,某个数取址取址一直被占的情况。

也可以采用其他方法计算哈希值,而且实际上应用中往往hash方法的参数不是数字,比如hash(String str){return (int)String.chart(0)*str.length-(int)String.chart(1)}[嗯,这个方法可能很差,因为是我乱写的]。

如果真的出现了一直取址被占的情况,只能说明你的hash算法设计得不够好,或者待测数据实在是巧合到诡异(就以上面的例子,如果待查序列中是{300,600,900,1200,1500,........9900}这样的话,后面的数想取址,注定会被占据多次)。但是死循环是不可能的,不信你看看find()函数怎么写的。

相信数据结构课没有听的你已经大概明白了hash的原理了,那么去看看HashMap源码吧,就不怕面试官咄咄逼人了。

猜你喜欢

转载自blog.csdn.net/qq_25929565/article/details/88980960