数组、链表、Hash(转)
在程序中,存放指定的数据最常用的数据结构有两种:数组和链表。
数组和链表的区别:
1、数组是将元素在内存中连续存放。
链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
2、数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况。
3、(静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小。
链表从堆中分配空间, 自由度大但是申请管理比较麻烦。
数组和链表在存储数据方面到底孰优孰劣呢?根据数组和链表的特性,分两类情况讨论。
一、当进行数据查询时,数组可以直接通过下标迅速访问数组中的元素。而链表则需要从第一个元素开始一直找到需要的元素位置,显然,数组的查询效率会比链表的高。
二、当进行增加或删除元素时,在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样,如果想删除一个元素,需要移动大量元素去填掉被移动的元素。而链表只需改动元素中的指针即可实现增加或删除元素。
那么,我们开始思考:有什么方式既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势?HASH呼之欲出。
所谓的hash,简单的说就是散列,即将输入的数据通过hash函数得到一个key值,输入的数据存储到数组中下标为key值的数组单元中去。
我们发现,不相同的数据通过hash函数得到相同的key值。这时候,就产生了hash冲突。
解决hash冲突的方式有两种。一种是挂链式,也叫拉链法。
挂链式的思想在产生冲突的hash地址指向一个链表,将具有相同的key值的数据存放到链表中。
另一种是建立一个公共溢出区。将所有产生冲突的数据都存放到公共溢出区,也可以使问题解决。
哈希冲突及四种解决方法
哈希冲突的产生原因
哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值。这时候就产生了哈希冲突。
产生哈希冲突的影响因素
装填因子(装填因子=数据总数 / 哈希表长)、哈希函数、处理冲突的方法
解决哈希冲突的四种方法
1.开放地址方法
(1)线性探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。
(2)再平方探测
按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
(3)伪随机探测
按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
2.链式地址法(HashMap的哈希冲突解决方法)
对于相同的值,使用链表进行连接。使用数组存储每一个链表。
优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:
指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
3.建立公共溢出区
建立公共溢出区存储所有哈希冲突的数据。
4.再哈希法
对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。
https://www.cnblogs.com/higerMan/p/11907117.html
如何实现hash的动态增加空间的效果?这和装在因子密切相关。装填因子 = 填入表中的元素个数 / 散列表的长度。当装填因子达到一定值a时,我们就让数组增加一定的内存空间,同时rehash。
下面用两个示例来加深理解。
示例一:用链表实现队列
节点类
public class LinkNode { //构造器:传入Object对象 public LinkNode(Object obj){ data=obj; } public Object data; //Object对象 public LinkNode next;//下一个节点 //重写toString方法 public String toString(){ //System.out.println(data); return (String)data; } //返回Object对象 public Object getData(){ return data; } //修改Onject对象 public Object Update(Object o){ data=o; return o; } }
队列类
public class LinkQueue { public LinkNode front=null;//第一个节点 public LinkNode last=null;//最后一个节点 public static void main(String args[]){ LinkQueue lq=new LinkQueue(); LinkNode lq1=new LinkNode("郑睿1"); LinkNode lq2=new LinkNode("郑睿2"); LinkNode lq3=new LinkNode("郑睿3"); LinkNode lq4=new LinkNode("郑睿4"); lq.InsertLinkNode(lq1); lq.InsertLinkNode(lq2); lq.InsertLinkNode(lq3); lq.InsertLinkNode(lq4); int count=lq.getLength(); System.out.println("链表的长度为"+count); for(int i=0;i<count;i++){ LinkNode ln = lq.getLinkNode(i); System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString()); } lq.deleteLinkNode(2); count=lq.getLength(); System.out.println("链表现在的长度是"+lq.getLength()); for(int i=0;i<count;i++){ LinkNode ln = lq.getLinkNode(i); System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString()); } lq.getLinkNode(1).Update("更新后的对象郑睿"); for(int i=0;i<count;i++){ LinkNode ln = lq.getLinkNode(i); System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString()); } for(int i=0;i<200;i++){ LinkNode ln = new LinkNode(i); lq.InsertLinkNode(ln); } System.out.println("数组长度为"+lq.getLength()); } /** * 插入节点 * @param obj:插入节点的对象 */ public void InsertLinkNode(Object obj){ //当链表为空,新建一个节点并设置为第一个节点 if(front==null){ front=new LinkNode(obj); last=front; } //当链表不为空,新建一个节点并插入到最后一个节点的后面 else{ LinkNode next=new LinkNode(obj); last.next=next; last=next; } } /** *在指定索引下插入节点 * @param index */ public void insertIndexObj(int index,Object obj){ //判断输入的索引是否越界,如果越界,则抛出异常 int total=getLength(); if(index>total||index<0) throw new java.lang.RuntimeException("输入的索引越界了!"); LinkNode lNode=getLinkNode(index); LinkNode linkNode=new LinkNode(obj); lNode.insert(linkNode); } /** * 根据索引删除链表 * @param index:索引 */ public void deleteLinkNode(int index){ //判断输入的索引是否越界,如果越界,则抛出异常 int total=getLength(); if(index>total||index<0) throw new java.lang.RuntimeException("输入的索引越界了!"); if(front!=null){ LinkNode n=front; LinkNode m=front; int count=0; while(n!=null){ if(count==index){ if(n.equals(front)){ front=front.next; } else{ m.next=n.next; } } m=n; n=n.next; count++; } } } /** * 根据索引取出节点 * @param lNode:节点 * @return:根据索引返回的节点 */ public LinkNode getLinkNode(int index){ if(front==null) return null; LinkNode l=front; int count=0; while(l!=null){ if(count==index) return l; count++; l=l.next; } return null; } /** * 得到链表的长度 * @return:链表的长度 */ public int getLength(){ if(front==null) return 0; LinkNode l=front; int count=0; while(l!=null){ count++; l=l.next; } return count; } /** * 修改对象节点 * @param index:对象节点索引 * @param obj:修改对象内容 */ public void UpdateLinkNode(int index,Object obj){ LinkNode lNode=getLinkNode(index); lNode.Update(obj); } }
示例二:保存QQ号码及QQ用户
QQ用户类
public class QQUser { public String userName;//用户姓名 public String passWord;//用户密码 public String sex;//用户性别 public int age;//用户年龄 public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
队列类
public class LinkQueue { public LinkNode front=null;//第一个节点 public LinkNode last=null;//最后一个节点 /** * 根据索引删除链表 * @param index:索引 */ public void deleteLinkNode(int index){ if(index<0||index>) if(front!=null){ LinkNode n=front; LinkNode m=front; int count=0; while(n!=null){ if(count==index){ if(n.equals(front)){ front=front.next; } else{ m.next=n.next; } } m=n; n=n.next; count++; } } } /** * 根据索引取出节点 * @param lNode:节点 * @return:根据索引返回的节点 */ public LinkNode getLinkNode(int index){ if(front==null) return null; LinkNode l=front; int count=0; while(l!=null){ if(count==index) return l; count++; l=l.next; } return null; } /** * 得到链表的长度 * @return:链表的长度 */ public int getLength(){ if(front==null) return 0; LinkNode l=front; int count=0; while(l!=null){ count++; l=l.next; } return count; } /** * 修改对象节点 * @param index:对象节点索引 * @param obj:修改对象内容 */ public void UpdateLinkNode(int index,Object obj){ LinkNode lNode=getLinkNode(index); lNode.Update(obj); } }
QQ节点类
public class QQNode { //构造器:传入QQ号,QQ用户对象 public QQNode(int qq,QQUser user){ this.qq=qq; this.user=user; } public int qq;//QQ号 public QQUser user;//QQ用户 public QQNode next;//下一个QQ节点对象 public LinkQueue lq;//队列 public LinkQueue getLq() { return lq; } public void setLq(LinkQueue lq) { this.lq = lq; } public int getQq() { return qq; } public void setQq(int qq) { this.qq = qq; } public QQUser getUser() { return user; } public void setUser(QQUser user) { this.user = user; } public QQNode getNext() { return next; } public void setNext(QQNode next) { this.next = next; } }
Hash方法类
public class QQHash { private QQNode[] table=new QQNode[100]; private float load=0.75F;//装载因子 private int count=0; private int gain=100; public static void main(String args[]){ QQHash qqHash=new QQHash(); QQUser user1=new QQUser(); user1.setUserName("用户一"); user1.setPassWord("1"); user1.setAge(20); user1.setSex("女"); qqHash.put(1, user1); QQUser user2=new QQUser(); user2.setUserName("用户二"); user2.setPassWord("12"); user2.setAge(20); user2.setSex("男"); qqHash.put(2, user2); QQUser user3=new QQUser(); user3.setUserName("用户三"); user3.setPassWord("123"); user3.setAge(20); user3.setSex("男"); qqHash.put(3, user3); QQUser user4=new QQUser(); user4.setUserName("用户四"); user4.setPassWord("1234"); user4.setAge(20); user4.setSex("女"); qqHash.put(101, user4); qqHash.returnQQNode(); user1=qqHash.get(1); user2=qqHash.get(2); user3=qqHash.get(3); user4=qqHash.get(101); QQNode[] table=qqHash.returnQQNode(); // System.out.println("表的长度为 "+table.length); qqHash.returnTabLen(); for(int i=0;i<table.length;i++){ if(table[i]!=null){ System.out.println("实际存在的Table["+i+"]的值"+table[i].getQq()); LinkQueue lq=table[i].getLq(); if(lq.getLength()>0){ System.out.println("存在挂链"); for(int j=0;j<lq.getLength();j++) System.out.println("挂链第"+i+"个值为"+((QQNode)lq.getLinkNode(i).getData()).getUser().getUserName()); } } } } /** * 存放QQ及用户 * @param qq:QQ号 * @param user:QQ用户 */ public void put(int qq,QQUser user){ //判断己放对象的个数和table的长度比是否达到装载因子, //如果超过,则reHash一次,增长, //然后再放! float rate=(float)count/table.length; if(rate>=load){ QQNode[] table1=new QQNode[table.length+gain]; for(int i=0;i<table.length;i++){ QQNode q=table[i]; int qqnum=q.getQq(); QQUser u=q.getUser(); int qqhash=hashQQ(qqnum); q.setQq(qqnum); q.setUser(user); table1[qqhash]=q; } table=table1; } System.out.println("table长度:"+table.length); //判断是否存在hash冲突 boolean judge=exist(qq); System.out.println("是否存在冲突"+judge); int index=hashQQ(qq); System.out.println("hash值"+index); if(judge){//不存在hash冲突,直接将qq和用户存放在通过hash函数获得的地址中 QQNode q=new QQNode(qq,user); q.setQq(qq); q.setUser(user); table[index]=q; count++; } else{//存在hash冲突 QQNode q=new QQNode(qq,user); q.setQq(qq); q.setUser(user); System.out.println(" "+q.getQq()+" "+q.getUser()); LinkQueue lq=q.getLq(); lq.InsertLinkNode(q); for(int i=0;i<lq.getLength();i++) System.out.println("======"+((QQNode)lq.getLinkNode(i).getData()).getQq()); if(lq.getLength()==0){ table[index].setNext(q); } } } /** * 根据QQ号取得QQ用户信息 * @param qq:QQ号 * @return:QQ用户 */ public QQUser get(int qq){ int index=hashQQ(qq); QQNode q=table[index]; System.out.println("节点"+q.getQq()); //看是否有下了个节点,如有,则是冲突的,就要一个一个比较 if(q.next==null) return q.getUser(); LinkQueue lq=q.getLq(); for(int i=0;i<lq.getLength();i++){ QQNode aa=(QQNode)lq.getLinkNode(i).data; int qqq=aa.getQq(); if(qqq==qq) System.out.println("查找到了!"); return aa.getUser(); } return null; } //计算QQ号的has值,自定义has函数 private int hashQQ(int qq){ return qq%table.length; } //判断是否存在hash冲突 private boolean exist(int qq){ int qqhash=hashQQ(qq); if(table[qqhash]!=null) return false; return true; } //返回表 private QQNode[] returnQQNode(){ System.out.println("已存在数据个数为"+count); return this.table; } //返回表中实际存在的数据的个数 private int returnTabLen(){ return this.count; } }
http://1029975378-qq-com.iteye.com/blog/814552
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步