在程序中,存放指定的数据最常用的数据结构有两种:数组和链表。
数组和链表的区别:
1、数组是将元素在内存中连续存放。
链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
2、数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况。
3、(静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小。
链表从堆中分配空间, 自由度大但是申请管理比较麻烦。
数组和链表在存储数据方面到底孰优孰劣呢?根据数组和链表的特性,分两类情况讨论。
一、当进行数据查询时,数组可以直接通过下标迅速访问数组中的元素。而链表则需要从第一个元素开始一直找到需要的元素位置,显然,数组的查询效率会比链表的高。
二、当进行增加或删除元素时,在数组中增加一个元素,需要移动大量元 素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样,如果想删除一个元素,需要移动大量元素去填掉被移动的元素。而链表只需改动元素中的指针即可实现增加或删除元素。
那么,我们开始思考:有什么方式既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势?HASH呼之欲出。
所谓的hash,简单的说就是散列,即将输入的数据通过hash函数得到一个key值,输入的数据存储到数组中下标为key值的数组单元中去。
我们发现,不相同的数据通过hash函数得到相同的key值。这时候,就产生了hash冲突。解决hash冲突的方式有两种。一种是挂链式,也叫拉链法。挂链式的思想在产生冲突的hash地址指向一个链表,将具有相同的key值的数据存放到链表中。另一种是建立一个公共溢出区。将所有产生冲突的数据都存放到公共溢出区,也可以使问题解决。
如何实现hash的动态增加空间的效果?这和装在因子密切相关。装填因子 = 填入表中的元素个数 / 散列表的长度。当装填因子达到一定值a时,我们就让数组增加一定的内存空间,同时rehash。
下面用两个示例来加深理解。
示例一:用链表实现队列
节点类
package cn.netjava.hash;
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;
}
}
队列类
package cn.netjava.hash;
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用户类
package cn.netjava.hash;
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;
}
}
队列类
package cn.netjava.hash;
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节点类
package cn.netjava.hash;
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方法类
package cn.netjava.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;
}
}