数据结构 六 :散列

一。对于查找表,我使用最多的莫过于Java中HashMap或者Python中的字典,即所谓的‘散列表’或者‘哈希表’,     是一种‘key-value ‘形式的树结构,记得大三时参加上海微创的面试,问了我一个算法题,然后我习惯性的使用HashMap进行了解答,之后面试官很鄙视的问我‘为什么要使用HashMap,而不使用数组’,当时我回答说‘因为HashMap是键值对的数据结构,处理起来方便一些’,如今回想起来,只能叹当时之才疏学浅,虽然现今也是功力尚浅。

    二。查找表,底层可以是链表或者顺序表进行实现。查找表的分类:

    (1)顺序查找表:

    这种类型的其中一种实现,它的概念有一点像操作系统中的分页置换算法,即把最新查询数据放到表尾,就像在一个书架,你肯定是把刚看过的书放在最边上,之后要找书的话就从最边上开始查。

    (2)有序查找表:

    对于有序查找表,顾名思义,其必定先是有序的,然后使用三个指针,依次进行查找,如:

    有  1  3  4  7  8  12  16  18  25 共9个元素  的有序表,

    当你要查找18的位置时,过程为:

          (i)三个指针刚开始分别指向1(a指针),8(b指针),25(c指针)  即分别是最左边,最中间,最右边

          (ii)因为18是在8和25之间即b指针和c指针之间,所以把a指针指向b,c的中间数字即16   

                                     (iii)因为18又是在16和25之间即a和c之间,所以把b指针指向a,c的中间数字即18,而此时正好找到需要             查找的数字

    示意图:
图片 

    示意图实在难于恭维,但也算是可以说明问题,望理解。

    (3)静态查找树表:有关于最优二叉树,不再详述

    (4)动态查找树表:不再详述

    (5)hash表:hash表是本文的重点

    三。hash:

    什么是哈希?不可否认,哈希应用范围非常广,如在IP分类递增的转发算法中即使用了hash算法,而在网络信息安全的安全加密则有更多的身影即MD4,MD5,和SHA1,又如Oracle中的索引以及之前我习惯性使用的只懂怎么用不懂其原理的HashMap,而nosql技术中也多少使用了hash。那么究竟什么是hash?

    在所有的数据结构中,应该来说数组的效率是最快的,因为当你声明一个一定尺寸的数组,则内存会为其开辟一个空间,而且是连续分配的,对于内存,我们想象它就是一个格子状的东西,每一个格子即每一个地址对应一个内容,而HashMap实质就是使用了数组进行表示,再实质一点就是‘用空间换时间’,不过应该很多所谓的提高效率的方法无非都是符合这个原则的,我们用例子来说明一下(以HashMap为例):

    比如你要储存 ‘abc’,‘acb’,‘bcd‘三个字符串,则你可以先定义一个hash函数,一般来说hash函数是根据你要存储的数据的结构来定义的,在这里我们假设hash函数为 

        int hash(key){

            return   x*12+y*7+z       //x,y,z分别为字符串的字符,在这里只是说明情况,没有考虑任何相关问题

        }

    假设我们的HashMap使用的数组为String str = new String[3];

    再假设'abc’,'acb’,'bcd’的hash值即hash('abc') ,hash('acb'),hash(’bcd‘)的值分别为3,4,5,为了不让下标超过数组尺寸,再对其分别取余(以数组长度为除数),即可以得出0,1,2, 则把这三个字符串分别放入str数组的0,1,2下标对应的位置

    数据已经完成存储过程,这时要对数据进行查询,如要查询'abc',则只要再做一遍hash与取余过程,得到数字0,则直接定位到数组的第0个下标处,至此整个过程结束。

    整个过程关键的无非是hash函数的确定,以得到一个‘好’hash值,另外也是为了防止‘冲突’,即有时候key1 != key2,但很可能hash(key1)==hash(key2),所以应该避免出现这种冲突情况,而万一出现的冲突,也有几个解决方案,如再hash法即再生成一个不一样的hash值,或者使用‘挂链法’,即对出现冲突情况,把这些数据在同一下标之后再使用链表连接起来,总之,冲突情况只能尽量避免不可能完全杜绝,而且冲突情况也会对整个效率有很大的影响,还有一个很重要的问题就是‘装载因子’,因为当底层的数组容量已满时,肯定需要增容,而装载因子就是一个增容幅度即‘加速度’,而这也会很大程度影响效率,因为当装载因子太大,则可能会使空间浪费过大,而装载因子太小,则可能出现频繁增容的情况,即频繁的数组内容转移。

    四。直接贴代码:

package com.ds.test4;

public class HashMap<K,V> {
	
	private Entry[] table ;              //底层的数组
	
	private int initCapacity ;           // 初始容量
	
	private double loadFactor ;          // 装载因子,一般是0-1之间
	
	private int size = 0;                //元素个数
	
	public HashMap(){
		this.initCapacity = 16;
		this.loadFactor = 0.75;
		table = new HashMap.Entry[initCapacity]; //内部类必须注明是哪个类,即Out.in
	}
	
	public void put(K k,V v){
		int hash = hash(k);
		checkCapcacity();                //判定容量
		size++;
		table[hash] = new Entry(hash,k,v);
	}
	
	private void checkCapcacity() {
		if(size>=initCapacity){
			initCapacity+=initCapacity*loadFactor;//使用装载因子进行增容
			Entry[] newTable = new HashMap.Entry[initCapacity];
			for(int i = 0;i<size;i++){
				newTable[i] = table[i];
			}
		}
	}

	public V get(K k){
		int hash = hash(k);   //先产生hash值,再查找
		return table[hash].v; 
	}
	
	public int hash(K k){
		int hash = k.hashCode();//使用的jdk的hashCode方法
		return hash;
	}
	
	class Entry{
		private int hash;
		private K k;
		private V v;
		public Entry(int hash,K k,V v){
			this.hash = hash;
			this.k = k;
			this.v = v;
		}			
	}
	
}

 

                    
    五。问题:

    (1)自己的代码只是简单的说明了一下问题,对于hash函数直接使用了jdk的hashCode方法。

    (2)也没有对get方法进行检查,因为当出现没有查询到元素的情况应该进行处理

    六。总结:

    知其然而不知其所以然,永远不会进步!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值