注意:哈希表在一定程度上可以看作一个缓存产品,但肯定没有redis…等产品那么强大
面试题
写一个实现:加入员工信息(id,姓名,年龄…),当输入员工id时,查到员工相关信息。要求不使用数据库,尽量节省内存并提高速度。
实现思路:
1. 将员工封装为类作为链表的Node
为每个员工写一个类,其中成员变量即为员工的个人信息,每个员工还应该与下一个员工形成链接关系,将每个员工理解为链表的每一个节点Node。
class Emp {
public int id;
public String name;
public Emp next; //next 默认为 null
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
2. 进行链表的实现
当链表的节点Node(即Emp)写好了后,开始写链表的类,目的是实现每个Node的add()、list()、find()、del()方法。
//创建EmpLinkedList ,表示链表
class EmpLinkedList {
//头指针,执行第一个Emp,因此我们这个链表的head 是直接指向第一个Emp
private Emp head; //默认null
//添加雇员到链表
//说明
//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.next = emp;
}
//遍历链表的雇员信息
public void list(int no) {
if(head == null) { //说明链表为空
System.out.println("第 "+(no+1)+" 链表为空");
return;
}
System.out.print("第 "+(no+1)+" 链表的信息为");
Emp curEmp = head; //辅助指针
while(true) {
System.out.printf(" => id=%d name=%s\t", curEmp.id, curEmp.name);
if(curEmp.next == null) {//说明curEmp已经是最后结点
break;
}
curEmp = curEmp.next; //后移,遍历
}
System.out.println();
}
//根据id查找雇员
//如果查找到,就返回Emp, 如果没有找到,就返回null
public Emp findEmpById(int id) {
//判断链表是否为空
if(head == null) {
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while(true) {
if(curEmp.id == id) {//找到
break;//这时curEmp就指向要查找的雇员
}
//退出
if(curEmp.next == null) {//说明遍历当前链表没有找到该雇员
curEmp = null;
break;
}
curEmp = curEmp.next;//以后
}
return curEmp;
}
}
3.对链表进行封装,对哈希表进行实现
- 当链表类写好后,应该开始写一个哈希表的类,目的是管理这些链表。其成员变量应该有 上述链表类型的数组(数组中每个元素存一条链表的引用,这就实现了哈希表的结构)。成员变量还可以定义一个size,在构造方法中就可以初始化链表的条数(上述数组的大小)。
- 这个哈希表也应该有add()、list()…等等方法,作为对外的总体封装。
以add()为例子,哈希表中的add()方法也调用了其中每个数组元素(类型为链表)的add()方法,哈希表多做的事情就是多定义一个数组的维度。 - 编写散列函数的作用是把所有员工散列地分配到每一条链表上。思路是除余。
//添加雇员
public void add(Emp emp) {
//根据员工的id ,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp 添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
总体实现代码如下:
class HashTab {
private EmpLinkedList[] empLinkedListArray;
private int size; //表示有多少条链表
//构造器
public HashTab(int size) {
this.size = size;
//初始化empLinkedListArray
empLinkedListArray = new EmpLinkedList[size];
//?留一个坑, 这时不要分别初始化每个链表
for(int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp) {
//根据员工的id ,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp 添加到对应的链表中
empLinkedListArray[empLinkedListNO].add(emp);
}
//遍历所有的链表,遍历hashtab
public void list() {
for(int i = 0; i < size; i++) {
empLinkedListArray[i].list(i);
}
}
//根据输入的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\n", (empLinkedListNO + 1), id);
}else{
System.out.println("在哈希表中,没有找到该雇员~");
}
}
/**
* 散列函数
* 1、散列函数的作用是把所有员工散列地分配到每一条链表上。
* 2、id=100则可以代表第100个员工。举例数组大小=10(10条链表)。如何把这第100个员工分到链表中?
* 100%10=0; 99%10=9; 98%10=8; ...
* 这样下来100个数每个都被均匀地分配到10条链路中(只要输入id,除余后就知道改分配到哪一条链路)
*
* 3、使用一个简单取模法的思路。(取余数)
*
* @param id id代表员工的编号,
* @return
*/
public int hashFun(int id) {
return id % size;
}
}
4.将以上模块进行整合
将以上模块进行整合后,对外部直接就表现为哈希表,直接进行测试就行了。