看一个实际需求,有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址..),当输入该员工的id时,要求查找到该员工的 所有信息。
要求: 不使用数据库,尽量节省内存,速度越快越好=>哈希表(散列)
1、散列表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
左侧是一条长为16的数组,每条数组又对应一条链表,这里的散列函数就是 X对16取余,余几,就把该数据放到对应的链表中。
这里有一个通俗的理解:0-15像我们英文字典的a-z,496、896等这些数据相当于a字母对应的单词。
2、用哈希表实现
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址..),当输入该员工的id时,要求查找到该员工的 所有信息。
要求: 不使用数据库,速度越快越好=>哈希表(散列) 添加时,保证按照id从低到高插入
(a)使用链表来实现哈希表, 该链表不带表头 [即: 链表的第一个结点就存放雇员信息]
(b)思路分析并画出示意图
(c)代码实现[增删改查(显示所有员工,按id查询)]
(1)创建员工类
class Emp{
public int id; //id
public String name; //姓名
public Emp next; //指向下一个员工,默认为空
//构造方法
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
(2)创建EmpLinkedList类,表示链表
class EmpLinkedList{
//头指针,指向第一个Emp,因此我们这个链表的head,是直接指向第一个Emp
private Emp head; //默认为空
//添加员工到链表
//说明
//1、假定,当添加雇员时,id是自增长的,即id的分配总是从小到大
//因此我们将员工直接加入到本链表最后即可
public void add(Emp emp){
//如果是添加第一个员工
if(head == null){
head = emp; //直接添加
return ;
}
//如果不是,则使用辅助指针,帮助定位到链表最后
Emp curEmp = head;
while(true){
if(curEmp.next == null){
//说明到链表最后了
break;
}
curEmp = curEmp.next; //指针后移
}
//【这段代码可以优化】。退出后,直接将emp添加到curEmp的尾部
/*
while(curEmp.next!=null){
curEmp = curEmp.next;
}
*/
curEmp.next = emp;
}
//2、遍历链表的员工信息
public void list(int no){
if(head == null){ //说明链表为空
System.out.println("第"+(no+1)+"链表为空......");
return ;
}
System.out.print("第"+(no+1)+"链表的信息为");
Emp curEmp = head; //辅助指针
while(curEmp!=null){
System.out.printf("=> id=%d,name=%s\t",curEmp.id,curEmp.name);
curEmp = curEmp.next; //移动指针
}
System.out.println();
}
//3、根据id查找员工信息
//如果查找到就返回Emp,如果没有找到就返回空
public Emp findEmpById(int id){
//判断链表是否为空
if(head == null){
//System.out.println("链表为空......");
return null;
}
//辅助指针
Emp curEmp = head;
while(true){
if(curEmp.id == id){ //找到
break;
}
if(curEmp.next == null){ //curEmp就是最后一个
//查找到最后一个了,还没有找到,说明没有该员工
curEmp = null;
break;
}
curEmp = curEmp.next; //移动指针
}
//出了上面这个while循环,curEmp要么为空,要么为所找到的员工
return curEmp;
}
//4、根据id删除员工信息
public void delEmpById(int id){
//判断是否为空
if(head == null){
System.out.println("没有该员工......");
return;
}
//如果是头结点
if(head.id == id){
System.out.printf("找到,已删除,其id=%d,name=%s\n",head.id,head.name);
head = head.next;
return;
}
//如果不是头结点
//辅助指针
Emp curEmp = head.next; //当前指针,即要删除的节点指针
Emp preEmp = head; //删除节点的前一个指针
while(true){
//找到
if(curEmp.id ==id){
preEmp.next = curEmp.next; //挂新链,即删除
System.out.printf("找到,已删除,其id=%d,name=%s\n",curEmp.id,curEmp.name);
break; //跳出
}
//到尾部了
if(curEmp==null){
System.out.println("没有找到......");
}
//移动两个指针
preEmp = preEmp.next;
curEmp = curEmp.next;
}
}
//5、根据id修改员工信息【思路是传入Emp,比较no,相同就覆盖name】
public void updateEmpById(Emp emp){
//判断为空
if(head == null){
System.out.println("链表为空......");
return ;
}
//辅助变量
Emp curEmp = head;
while(true){
//找到
if(curEmp.id == emp.id){
curEmp.name = emp.name; //修改name
System.out.println("修改成功.....");
return;
}
if(curEmp.next == null){
//最后位置
System.out.println("没有找到该id");
return;
}
curEmp = curEmp.next; //移动指针
}
}
}
这个类是对Emp对象进行操作,包括了增删改查遍历。
(3)创建HashTab,管理多条链表
class hashTab{
//empLinkedListArray是一个数组,里面放的数据是EmpLinkedList类型
private EmpLinkedList[] empLinkedListArray;
private int size; //表示链表数量
//1、构造函数
public hashTab(int size){
this.size = size;
//初始化empLinkedListArray
empLinkedListArray = new EmpLinkedList[size];
//?不要忘了分别初始化每一条链表
for(int i=0;i<size;i++){
empLinkedListArray[i] = new EmpLinkedList();
}
}
//3、添加员工
public void add(Emp emp){
//根据员工的id,得到该员工应该添加到哪条链表
int empLinkedListNo = hashFun(emp.id);
//将emp加入到对应的链表中
empLinkedListArray[empLinkedListNo].add(emp);
}
//4、遍历所有的链表,遍历哈希表
public void list(){
for(int i=0;i<size;i++){
empLinkedListArray[i].list(i);
}
}
//5、根据输入的id查找员工信息
public void findEmpById(int id){
//使用散列函数,确定到哪条链表查找
int empLinkedListNo = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNo].findEmpById(id);
if(emp!=null){
//找到
System.out.printf("在第%d条链表中找到该员工,id=%d,name=%s\n",empLinkedListNo+1,emp.id,emp.name);
}else{
//没有找到
System.out.println("没有找到该员工......");
}
}
//6、根据输入的id删除员工信息
public void delEmpById(int id){
int empLinkedListNo = hashFun(id);
empLinkedListArray[empLinkedListNo].delEmpById(id);
}
//7、根据输入的id修改员工信息
public void updateEmpById(Emp emp){
int empLinkedListNo = hashFun(emp.id);
empLinkedListArray[empLinkedListNo].updateEmpById(emp);
}
//2、散列函数,使用取模法
public int hashFun(int id) {
return id % size;
}
}
这里的方法可以和EmpLinkedList类中的方法对应逐步写,方便调试。
(4)测试
public static void main(String[] args) {
//测试
//创建哈希表,7条链表
hashTab hashtab = new hashTab(7);
//写一个简单菜单
String key = "";
Scanner scanner= new Scanner(System.in);
while(true){
System.out.println("a:添加员工");
System.out.println("l:显示员工");
System.out.println("f:查找员工");
System.out.println("d:删除员工");
System.out.println("u:修改员工");
System.out.println("e:退出系统");
key = scanner.next();
char ch = key.charAt(0); // 字符串转字符
switch (ch) {
case 'a':
System.out.println("输入id:");
int id = scanner.nextInt();
System.out.println("输入名字:");
String name = scanner.next();
// 创建员工
Emp emp = new Emp(id, name);
// 添加
hashtab.add(emp);
break;
case 'l':
hashtab.list();
break;
case 'f':
System.out.println("输入要查找的id:");
id = scanner.nextInt();
hashtab.findEmpById(id);
break;
case 'd':
System.out.println("输入要删除的id:");
id = scanner.nextInt();
hashtab.delEmpById(id);
break;
case 'u':
System.out.println("输入要修改的id:");
id = scanner.nextInt();
System.out.println("请输入要修改输入名字:");
name = scanner.next();
emp = new Emp(id,name);
hashtab.updateEmpById(emp);
break;
case 'e':
scanner.close();
System.exit(0);
default:
break;
}
}
}
}
哈希表还是很难理解的。