数组、链表、Hash

      在程序中,存放指定的数据最常用的数据结构有两种:数组和链表。

      数组和链表的区别:

      1、数组是将元素在内存中连续存放。

           链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。

      2、数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。

           链表动态地进行存储分配,可以适应数据动态地增减的情况。

      3、(静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小。

     链表从堆中分配空间, 自由度大但是申请管理比较麻烦。

数组和链表在存储数据方面到底孰优孰劣呢?根据数组和链表的特性,分两类情况讨论。

一、当进行数据查询时,数组可以直接通过下标迅速访问数组中的元素。而链表则需要从第一个元素开始一直找到需要的元素位置,显然,数组的查询效率会比链表的高。

二、当进行增加或删除元素时,在数组中增加一个元素,需要移动大量元 素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样,如果想删除一个元素,需要移动大量元素去填掉被移动的元素。而链表只需改动元素中的指针即可实现增加或删除元素。

那么,我们开始思考:有什么方式既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势?HASH呼之欲出。

所谓的hash,简单的说就是散列,即将输入的数据通过hash函数得到一个key值,输入的数据存储到数组中下标为key值的数组单元中去。

我们发现,不相同的数据通过hash函数得到相同的key值。这时候,就产生了hash冲突。解决hash冲突的方式有两种。一种是挂链式,也叫拉链法。挂链式的思想在产生冲突的hash地址指向一个链表,将具有相同的key值的数据存放到链表中。另一种是建立一个公共溢出区。将所有产生冲突的数据都存放到公共溢出区,也可以使问题解决。

如何实现hash的动态增加空间的效果?这和装在因子密切相关。装填因子 = 填入表中的元素个数 / 散列表的长度。当装填因子达到一定值a时,我们就让数组增加一定的内存空间,同时rehash。

 

下面用两个示例来加深理解。

 

示例一:用链表实现队列

 

节点类

 

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class LinkNode {  
  4.   
  5.     //构造器:传入Object对象  
  6.   
  7.     public LinkNode(Object obj){  
  8.         data=obj;  
  9.     }  
  10.     public Object data; //Object对象  
  11.     public LinkNode next;//下一个节点  
  12.   
  13.         //重写toString方法  
  14.   
  15.     public String toString(){  
  16.         //System.out.println(data);  
  17.         return (String)data;  
  18.     }  
  19.   
  20.        //返回Object对象  
  21.     public Object getData(){  
  22.         return data;  
  23.     }  
  24.         //修改Onject对象  
  25.     public Object Update(Object o){  
  26.         data=o;  
  27.         return o;  
  28.     }  
  29. }  
 

 

队列类

 

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class LinkQueue {  
  4.       
  5.     public LinkNode front=null;//第一个节点  
  6.     public LinkNode last=null;//最后一个节点  
  7.       
  8.     public static void main(String args[]){  
  9.         LinkQueue lq=new LinkQueue();  
  10.         LinkNode lq1=new LinkNode("郑睿1");  
  11.         LinkNode lq2=new LinkNode("郑睿2");  
  12.         LinkNode lq3=new LinkNode("郑睿3");  
  13.         LinkNode lq4=new LinkNode("郑睿4");  
  14.         lq.InsertLinkNode(lq1);  
  15.         lq.InsertLinkNode(lq2);  
  16.         lq.InsertLinkNode(lq3);  
  17.         lq.InsertLinkNode(lq4);  
  18.         int count=lq.getLength();  
  19.         System.out.println("链表的长度为"+count);  
  20.         for(int i=0;i<count;i++){  
  21.             LinkNode ln = lq.getLinkNode(i);  
  22.             System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString());  
  23.         }  
  24.         lq.deleteLinkNode(2);  
  25.         count=lq.getLength();  
  26.         System.out.println("链表现在的长度是"+lq.getLength());  
  27.         for(int i=0;i<count;i++){  
  28.             LinkNode ln = lq.getLinkNode(i);  
  29.             System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString());  
  30.         }  
  31.         lq.getLinkNode(1).Update("更新后的对象郑睿");  
  32.         for(int i=0;i<count;i++){  
  33.             LinkNode ln = lq.getLinkNode(i);  
  34.             System.out.println("链表的第"+i+"个元素的的值为"+ln.getData().toString());  
  35.         }  
  36.         for(int i=0;i<200;i++){  
  37.             LinkNode ln = new LinkNode(i);  
  38.             lq.InsertLinkNode(ln);  
  39.         }  
  40.         System.out.println("数组长度为"+lq.getLength());  
  41.     }  
  42.       
  43.   
  44.     /** 
  45.      * 插入节点 
  46.      * @param obj:插入节点的对象 
  47.      */  
  48.     public void InsertLinkNode(Object obj){  
  49.           
  50.         //当链表为空,新建一个节点并设置为第一个节点  
  51.         if(front==null){  
  52.             front=new LinkNode(obj);  
  53.             last=front;  
  54.         }  
  55.         //当链表不为空,新建一个节点并插入到最后一个节点的后面  
  56.         else{  
  57.             LinkNode next=new LinkNode(obj);  
  58.             last.next=next;  
  59.             last=next;  
  60.         }  
  61.     }  
  62.     /** 
  63.      *在指定索引下插入节点 
  64.      * @param index 
  65.      */  
  66.     public void insertIndexObj(int index,Object obj){  
  67.         //判断输入的索引是否越界,如果越界,则抛出异常  
  68.         int total=getLength();        
  69.         if(index>total||index<0)  
  70.             throw new java.lang.RuntimeException("输入的索引越界了!");  
  71.         LinkNode lNode=getLinkNode(index);  
  72.         LinkNode linkNode=new LinkNode(obj);  
  73.         lNode.insert(linkNode);  
  74.           
  75.     }  
  76.     /** 
  77.      * 根据索引删除链表 
  78.      * @param index:索引 
  79.      */  
  80.     public void deleteLinkNode(int index){  
  81.           
  82.         //判断输入的索引是否越界,如果越界,则抛出异常  
  83.         int total=getLength();        
  84.         if(index>total||index<0)  
  85.             throw new java.lang.RuntimeException("输入的索引越界了!");  
  86.         if(front!=null){  
  87.         LinkNode n=front;  
  88.         LinkNode m=front;  
  89.         int count=0;  
  90.         while(n!=null){  
  91.             if(count==index){  
  92.                 if(n.equals(front)){  
  93.                     front=front.next;  
  94.                 }  
  95.                 else{  
  96.                     m.next=n.next;  
  97.                 }  
  98.             }  
  99.             m=n;  
  100.             n=n.next;  
  101.             count++;  
  102.         }  
  103.         }  
  104.     }  
  105.     /** 
  106.      * 根据索引取出节点 
  107.      * @param lNode:节点 
  108.      * @return:根据索引返回的节点 
  109.      */  
  110.     public LinkNode getLinkNode(int index){  
  111.         if(front==null)  
  112.         return null;  
  113.         LinkNode l=front;  
  114.         int count=0;  
  115.         while(l!=null){  
  116.             if(count==index)  
  117.             return l;  
  118.             count++;  
  119.             l=l.next;  
  120.         }  
  121.         return null;  
  122.     }  
  123.       
  124.     /** 
  125.      * 得到链表的长度 
  126.      * @return:链表的长度 
  127.      */  
  128.     public int getLength(){  
  129.         if(front==null)  
  130.             return 0;  
  131.         LinkNode l=front;  
  132.         int count=0;  
  133.         while(l!=null){  
  134.             count++;  
  135.             l=l.next;  
  136.         }  
  137.         return count;  
  138.     }  
  139.     /** 
  140.      * 修改对象节点 
  141.      * @param index:对象节点索引 
  142.      * @param obj:修改对象内容 
  143.      */  
  144.     public void UpdateLinkNode(int index,Object obj){  
  145.         LinkNode lNode=getLinkNode(index);  
  146.         lNode.Update(obj);  
  147.           
  148.     }  
  149. }  
 

 

 

 

示例二:保存QQ号码及QQ用户

 

 

QQ用户类

 

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class QQUser {  
  4.       
  5.     public String userName;//用户姓名  
  6.     public String passWord;//用户密码  
  7.     public String sex;//用户性别  
  8.     public int age;//用户年龄  
  9.   
  10.     public String getUserName() {  
  11.         return userName;  
  12.     }  
  13.     public void setUserName(String userName) {  
  14.         this.userName = userName;  
  15.     }  
  16.     public String getPassWord() {  
  17.         return passWord;  
  18.     }  
  19.     public void setPassWord(String passWord) {  
  20.         this.passWord = passWord;  
  21.     }  
  22.     public String getSex() {  
  23.         return sex;  
  24.     }  
  25.     public void setSex(String sex) {  
  26.         this.sex = sex;  
  27.     }  
  28.     public int getAge() {  
  29.         return age;  
  30.     }  
  31.     public void setAge(int age) {  
  32.         this.age = age;  
  33.     }  
  34.       
  35. }  

 

 

 

队列类

 

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class LinkQueue {  
  4.       
  5.     public LinkNode front=null;//第一个节点  
  6.     public LinkNode last=null;//最后一个节点  
  7.       
  8.   
  9.     /** 
  10.      * 根据索引删除链表 
  11.      * @param index:索引 
  12.      */  
  13.     public void deleteLinkNode(int index){  
  14.                 if(index<0||index>)  
  15.         if(front!=null){  
  16.         LinkNode n=front;  
  17.         LinkNode m=front;  
  18.         int count=0;  
  19.         while(n!=null){  
  20.             if(count==index){  
  21.                 if(n.equals(front)){  
  22.                     front=front.next;  
  23.                 }  
  24.                 else{  
  25.                     m.next=n.next;  
  26.                 }  
  27.             }  
  28.             m=n;  
  29.             n=n.next;  
  30.             count++;  
  31.         }  
  32.         }  
  33.     }  
  34.     /** 
  35.      * 根据索引取出节点 
  36.      * @param lNode:节点 
  37.      * @return:根据索引返回的节点 
  38.      */  
  39.     public LinkNode getLinkNode(int index){  
  40.         if(front==null)  
  41.         return null;  
  42.         LinkNode l=front;  
  43.         int count=0;  
  44.         while(l!=null){  
  45.             if(count==index)  
  46.             return l;  
  47.             count++;  
  48.             l=l.next;  
  49.         }  
  50.         return null;  
  51.     }  
  52.       
  53.     /** 
  54.      * 得到链表的长度 
  55.      * @return:链表的长度 
  56.      */  
  57.     public int getLength(){  
  58.         if(front==null)  
  59.             return 0;  
  60.         LinkNode l=front;  
  61.         int count=0;  
  62.         while(l!=null){  
  63.             count++;  
  64.             l=l.next;  
  65.         }  
  66.         return count;  
  67.     }  
  68.     /** 
  69.      * 修改对象节点 
  70.      * @param index:对象节点索引 
  71.      * @param obj:修改对象内容 
  72.      */  
  73.     public void UpdateLinkNode(int index,Object obj){  
  74.         LinkNode lNode=getLinkNode(index);  
  75.         lNode.Update(obj);  
  76.           
  77.     }  
  78. }  

 

 

QQ节点类

 

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class QQNode {  
  4.   
  5.   
  6.         //构造器:传入QQ号,QQ用户对象  
  7.     public QQNode(int qq,QQUser user){  
  8.         this.qq=qq;  
  9.         this.user=user;  
  10.     }  
  11.       
  12.     public int qq;//QQ号  
  13.     public QQUser user;//QQ用户  
  14.     public QQNode next;//下一个QQ节点对象  
  15.     public LinkQueue lq;//队列  
  16.   
  17.     public LinkQueue getLq() {  
  18.         return lq;  
  19.     }  
  20.     public void setLq(LinkQueue lq) {  
  21.         this.lq = lq;  
  22.     }  
  23.     public int getQq() {  
  24.         return qq;  
  25.     }  
  26.     public void setQq(int qq) {  
  27.         this.qq = qq;  
  28.     }  
  29.     public QQUser getUser() {  
  30.         return user;  
  31.     }  
  32.     public void setUser(QQUser user) {  
  33.         this.user = user;  
  34.     }  
  35.     public QQNode getNext() {  
  36.         return next;  
  37.     }  
  38.     public void setNext(QQNode next) {  
  39.         this.next = next;  
  40.     }  
  41.       
  42. }  
 

 

Hash方法类

Java代码   收藏代码
  1. package cn.netjava.hash;  
  2.   
  3. public class QQHash {  
  4.       
  5.     private QQNode[] table=new QQNode[100];  
  6.     private float load=0.75F;//装载因子  
  7.     private int count=0;  
  8.     private int gain=100;  
  9.       
  10.     public static void main(String args[]){  
  11.         QQHash qqHash=new QQHash();  
  12.         QQUser user1=new QQUser();  
  13.         user1.setUserName("用户一");  
  14.         user1.setPassWord("1");  
  15.         user1.setAge(20);  
  16.         user1.setSex("女");  
  17.         qqHash.put(1, user1);  
  18.         QQUser user2=new QQUser();  
  19.         user2.setUserName("用户二");  
  20.         user2.setPassWord("12");  
  21.         user2.setAge(20);  
  22.         user2.setSex("男");  
  23.         qqHash.put(2, user2);  
  24.         QQUser user3=new QQUser();  
  25.         user3.setUserName("用户三");  
  26.         user3.setPassWord("123");  
  27.         user3.setAge(20);  
  28.         user3.setSex("男");  
  29.         qqHash.put(3, user3);  
  30.         QQUser user4=new QQUser();  
  31.         user4.setUserName("用户四");  
  32.         user4.setPassWord("1234");  
  33.         user4.setAge(20);  
  34.         user4.setSex("女");  
  35.         qqHash.put(101, user4);  
  36.         qqHash.returnQQNode();  
  37.           
  38.         user1=qqHash.get(1);          
  39.         user2=qqHash.get(2);  
  40.         user3=qqHash.get(3);  
  41.         user4=qqHash.get(101);  
  42.         QQNode[] table=qqHash.returnQQNode();  
  43. //      System.out.println("表的长度为   "+table.length);  
  44.         qqHash.returnTabLen();  
  45.         for(int i=0;i<table.length;i++){  
  46.             if(table[i]!=null){  
  47.             System.out.println("实际存在的Table["+i+"]的值"+table[i].getQq());   
  48.             LinkQueue lq=table[i].getLq();  
  49.             if(lq.getLength()>0){      
  50.                 System.out.println("存在挂链");  
  51.                 for(int j=0;j<lq.getLength();j++)  
  52.                     System.out.println("挂链第"+i+"个值为"+((QQNode)lq.getLinkNode(i).getData()).getUser().getUserName());  
  53.             }  
  54.             }  
  55.               
  56.         }  
  57.           
  58.     }  
  59.     /** 
  60.      * 存放QQ及用户 
  61.      * @param qq:QQ号 
  62.      * @param user:QQ用户 
  63.      */  
  64.     public void put(int qq,QQUser user){  
  65.         //判断己放对象的个数和table的长度比是否达到装载因子,  
  66.         //如果超过,则reHash一次,增长,  
  67.         //然后再放!  
  68.         float rate=(float)count/table.length;  
  69.         if(rate>=load){  
  70.             QQNode[] table1=new QQNode[table.length+gain];  
  71.             for(int i=0;i<table.length;i++){  
  72.                 QQNode q=table[i];  
  73.                 int qqnum=q.getQq();  
  74.                 QQUser u=q.getUser();  
  75.                 int qqhash=hashQQ(qqnum);  
  76.                 q.setQq(qqnum);  
  77.                 q.setUser(user);  
  78.                 table1[qqhash]=q;  
  79.             }  
  80.             table=table1;  
  81.         }  
  82.         System.out.println("table长度:"+table.length);  
  83.         //判断是否存在hash冲突  
  84.         boolean judge=exist(qq);  
  85.         System.out.println("是否存在冲突"+judge);  
  86.         int index=hashQQ(qq);  
  87.         System.out.println("hash值"+index);  
  88.         if(judge){//不存在hash冲突,直接将qq和用户存放在通过hash函数获得的地址中  
  89.             QQNode q=new QQNode(qq,user);  
  90.             q.setQq(qq);  
  91.             q.setUser(user);  
  92.             table[index]=q;  
  93.             count++;  
  94.         }  
  95.         else{//存在hash冲突  
  96.             QQNode q=new QQNode(qq,user);  
  97.             q.setQq(qq);  
  98.             q.setUser(user);  
  99.             System.out.println("   "+q.getQq()+"  "+q.getUser());  
  100.             LinkQueue lq=q.getLq();  
  101.             lq.InsertLinkNode(q);  
  102.             for(int i=0;i<lq.getLength();i++)  
  103.                 System.out.println("======"+((QQNode)lq.getLinkNode(i).getData()).getQq());  
  104.             if(lq.getLength()==0){  
  105.             table[index].setNext(q);  
  106.             }  
  107.         }  
  108.           
  109.     }  
  110.     /** 
  111.      * 根据QQ号取得QQ用户信息 
  112.      * @param qq:QQ号 
  113.      * @return:QQ用户 
  114.      */  
  115.     public QQUser get(int qq){  
  116.         int index=hashQQ(qq);  
  117.         QQNode q=table[index];  
  118.         System.out.println("节点"+q.getQq());  
  119.         //看是否有下了个节点,如有,则是冲突的,就要一个一个比较  
  120.         if(q.next==null)  
  121.             return q.getUser();  
  122.         LinkQueue lq=q.getLq();  
  123.         for(int i=0;i<lq.getLength();i++){  
  124.             QQNode aa=(QQNode)lq.getLinkNode(i).data;  
  125.             int qqq=aa.getQq();  
  126.             if(qqq==qq)  
  127.                 System.out.println("查找到了!");  
  128.                 return aa.getUser();  
  129.         }  
  130.         return null;  
  131.     }  
  132.     //计算QQ号的has值,自定义has函数  
  133.     private int hashQQ(int qq){  
  134.         return qq%table.length;  
  135.     }  
  136.     //判断是否存在hash冲突  
  137.     private boolean exist(int qq){  
  138.         int qqhash=hashQQ(qq);  
  139.         if(table[qqhash]!=null)  
  140.             return false;  
  141.         return true;  
  142.     }  
  143.     //返回表  
  144.     private QQNode[] returnQQNode(){  
  145.         System.out.println("已存在数据个数为"+count);  
  146.         return this.table;  
  147.     }  
  148.     //返回表中实际存在的数据的个数  
  149.     private int returnTabLen(){  
  150.         return this.count;  
  151.     }  
  152. }  
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
声明:使用这些类是使用者的自愿行为,作者对源代码的质量不提供任何形式的担保,如果使用者因使用这些类而造成的任何损失都与作者无关,作者不承担任何责任。<br><br>/*************** 这些头文件不必包含 ***************/<br>node.h: 普通链表结点<br>dnode.h: 双向循环链表结点<br>treenode.h: 二叉树结点<br>avltreenode.h: AVL 树结点<br>/**************************************************/<br><br>array.h: 安全数组,可自动增长大小(随机访问,但扩充时效率低)<br>linkedlist.h: 普通链表(可随机访问,但访问效率低)<br>dclinkedlist: 双向循环链表(不可随机访问,但插入、遍历的效率都比普通链表高)<br>hashtable.h: 哈希表(使用键值标识元素,键值一样的元素即认为相等,需重载 == 运算符并由用户定义哈希函数)<br>binstree.h: 二叉搜索树(需重载 == 和 < 运算符)<br>avltree.h: AVL 树(需重载 == 和 < 运算符)<br><br>如果要存储集合(元素不可重复)并快速查找,最佳的是 binstree.h(二叉搜索树)。<br>如果要存储二维或更高维的表格,最佳的是 hashtable.h(哈系表)。<br><br>AVL 树的插入成本非常高(删除函数也没有实现),但 AVL 的搜索效率极高,所以适用于在程序开始前初始化程序中经常要用到的集合,一般应用二叉搜索树已经足够了。<br><br>以上代码都是作者照书上改写的,并未经过严格测试,如果使用过程中发现任何问题、源代码错误或可改进的地方,非常欢迎来信与我讨论。电子邮件地址:[email protected]<br><br>作者会根据各位所发现的问题不断改进各类并增加新的数据结构,使其更加完善。<br><br>参考书目:<br>《数据结构-C++ 语言描述》 William Ford William Topp 著 清华大学出版社<br>《计算机程序设计艺术》 DONALD E.KNUTH 著 清华大学出版社<br>
hash表的源代码#include <stdio.h> /*标准输入输出函数库*/ #include<stdlib.h> /*标准函数库*/ #include<string.h> #define HASH_LEN 50 /*哈希表的长度 */ #define M 47 #define NAME_N 30 /*人名拼音的最大个数*/ typedef struct NAME { char *py; /*名字的拼音*/ int k; /*拼音所对应的整数*/ }NAME; NAME NameList[HASH_LEN]; /*定义一个NAME类型的一维结构体数组*/ typedef struct hterm /*定义一个结构体类型hterm ,用typedef语句定义一个新类型HASH一个哈希表*/ { char *py; /*名字的拼音*/ int k; /*拼音所对应的整数 */ int si; /*查找长度 */ }HASH; HASH HashList[HASH_LEN]; /*定义HASH类型的一维数组*/ //创建一个姓名链表 void CreateNameList() /*创建姓名链表赋值*/ { NameList[0].py="liudan"; NameList[1].py="yanfanglei"; NameList[2].py="sunwei"; NameList[3].py="muyunfei"; NameList[4].py="wuyuanyuan"; NameList[5].py="weixing"; NameList[6].py="hefanrong"; NameList[7].py="wangxiaotian"; NameList[8].py="zhoulei"; NameList[9].py="houcuncun"; NameList[10].py="zhangliang"; NameList[11].py="songyangyang"; NameList[12].py="tianhuanhuan"; NameList[13].py="renkun"; NameList[14].py="sungang"; NameList[15].py="fuxiaohui"; NameList[16].py="qinlong"; NameList[17].py="gaodan"; NameList[18].py="andongmei"; NameList[19].py="wanglintao"; NameList[20].py="wangyalan"; NameList[21].py="limenglu"; NameList[22].py="wangxin"; NameList[23].py="zhangnana"; NameList[24].py="shirui"; NameList[25].py="wangdong"; NameList[26].py="majunchao"; NameList[27].py="wanghuanhuan"; NameList[28].py="wangni"; NameList[29].py="heqi";

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值